From 36c8f0e643c82fa157fe373d9a0442dfa7e67afb Mon Sep 17 00:00:00 2001 From: Guilhem Fanton <8671905+gfanton@users.noreply.github.com> Date: Mon, 13 Jan 2025 00:10:04 +0100 Subject: [PATCH 01/20] feat: add account number / sequence as env in txtar (#3477) --- gno.land/pkg/integration/doc.go | 14 +- .../pkg/integration/testdata/addpkg.txtar | 4 +- .../testdata/addpkg_namespace.txtar | 12 +- .../integration/testdata/adduserfrom.txtar | 4 +- .../testdata/event_multi_msg.txtar | 9 +- .../pkg/integration/testdata/gnokey.txtar | 11 +- .../testdata/gnokey_simulate.txtar | 20 +- .../pkg/integration/testdata/patchpkg.txtar | 4 +- .../pkg/integration/testdata/prevrealm.txtar | 30 +-- .../realm_banker_issued_coin_denom.txtar | 16 +- .../pkg/integration/testscript_gnoland.go | 205 +++++++++--------- gno.land/pkg/integration/utils.go | 73 +++++++ ...stscript_gnoland_test.go => utils_test.go} | 0 13 files changed, 236 insertions(+), 166 deletions(-) create mode 100644 gno.land/pkg/integration/utils.go rename gno.land/pkg/integration/{testscript_gnoland_test.go => utils_test.go} (100%) diff --git a/gno.land/pkg/integration/doc.go b/gno.land/pkg/integration/doc.go index 3e09d627c9a..d93d4607a59 100644 --- a/gno.land/pkg/integration/doc.go +++ b/gno.land/pkg/integration/doc.go @@ -102,11 +102,17 @@ // The path where the gnoland node stores its configuration and data. It's // set only if the node has started. // -// - USER_SEED_test1: -// Contains the seed for the test1 account. +// - xxx_user_seed: +// Where `xxx` is the account name; Contains the seed for the test1 account. // -// - USER_ADDR_test1: -// Contains the address for the test1 account. +// - xxx_user_addr: +// Where `xxx` is the account name; Contains the address for the test1 account. +// +// - xxx_account_num: +// Where `xxx` is the account name; Contains the account number for the test1 account. +// +// - xxx_account_seq: +// Where `xxx` is the account name; Contains the address for the test1 account. // // - RPC_ADDR: // Points to the gnoland node's remote address. It's set only if the node has started. diff --git a/gno.land/pkg/integration/testdata/addpkg.txtar b/gno.land/pkg/integration/testdata/addpkg.txtar index 8594e6596ce..15e8ad222f2 100644 --- a/gno.land/pkg/integration/testdata/addpkg.txtar +++ b/gno.land/pkg/integration/testdata/addpkg.txtar @@ -4,7 +4,7 @@ gnoland start ## deploy realm -gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/$USER_ADDR_test1/hello -gas-fee 1000000ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/$test1_user_addr/hello -gas-fee 1000000ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 ## check output stdout OK! @@ -15,7 +15,7 @@ stdout 'EVENTS: \[\]' stdout 'TX HASH: ' ## call added realm -gnokey maketx call -pkgpath gno.land/r/$USER_ADDR_test1/hello -chainid=tendermint_test -func SayHello -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast test1 +gnokey maketx call -pkgpath gno.land/r/$test1_user_addr/hello -chainid=tendermint_test -func SayHello -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast test1 ## check output stdout '\("hello world!" string\)' diff --git a/gno.land/pkg/integration/testdata/addpkg_namespace.txtar b/gno.land/pkg/integration/testdata/addpkg_namespace.txtar index 89da8a51820..f529c176f36 100644 --- a/gno.land/pkg/integration/testdata/addpkg_namespace.txtar +++ b/gno.land/pkg/integration/testdata/addpkg_namespace.txtar @@ -4,7 +4,7 @@ loadpkg gno.land/r/sys/users adduser admin adduser gui -patchpkg "g1manfred47kzduec920z88wfr64ylksmdcedlf5" $USER_ADDR_admin # use our custom admin +patchpkg "g1manfred47kzduec920z88wfr64ylksmdcedlf5" $admin_user_addr # use our custom admin gnoland start @@ -20,7 +20,7 @@ stdout 'false' # Gui should be able to addpkg on test1 addr # gui addpkg -> gno.land/r//mysuperpkg -gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/$USER_ADDR_test1/mysuperpkg -gas-fee 1000000ugnot -gas-wanted 400000 -broadcast -chainid=tendermint_test gui +gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/$test1_user_addr/mysuperpkg -gas-fee 1000000ugnot -gas-wanted 400000 -broadcast -chainid=tendermint_test gui stdout 'OK!' # Gui should be able to addpkg on random name @@ -43,12 +43,12 @@ stdout 'true' # Try to add a pkg an with unregistered user # gui addpkg -> gno.land/r//one -! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/$USER_ADDR_test1/one -gas-fee 1000000ugnot -gas-wanted 1000000 -broadcast -chainid=tendermint_test gui +! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/$test1_user_addr/one -gas-fee 1000000ugnot -gas-wanted 1000000 -broadcast -chainid=tendermint_test gui stderr 'unauthorized user' # Try to add a pkg with an unregistered user, on their own address as namespace # gui addpkg -> gno.land/r//one -gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/$USER_ADDR_gui/one -gas-fee 1000000ugnot -gas-wanted 1000000 -broadcast -chainid=tendermint_test gui +gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/$gui_user_addr/one -gas-fee 1000000ugnot -gas-wanted 1000000 -broadcast -chainid=tendermint_test gui stdout 'OK!' ## Test unregistered namespace @@ -63,12 +63,12 @@ stderr 'unauthorized user' # Test admin invites gui # admin call -> demo/users.Invite -gnokey maketx call -pkgpath gno.land/r/demo/users -func Invite -gas-fee 1000000ugnot -gas-wanted 2500000 -broadcast -chainid=tendermint_test -args $USER_ADDR_gui admin +gnokey maketx call -pkgpath gno.land/r/demo/users -func Invite -gas-fee 1000000ugnot -gas-wanted 2500000 -broadcast -chainid=tendermint_test -args $gui_user_addr admin stdout 'OK!' # test gui register namespace # gui call -> demo/users.Register -gnokey maketx call -pkgpath gno.land/r/demo/users -func Register -gas-fee 1000000ugnot -gas-wanted 2500000 -broadcast -chainid=tendermint_test -args $USER_ADDR_admin -args 'guiland' -args 'im gui' gui +gnokey maketx call -pkgpath gno.land/r/demo/users -func Register -gas-fee 1000000ugnot -gas-wanted 2500000 -broadcast -chainid=tendermint_test -args $admin_user_addr -args 'guiland' -args 'im gui' gui stdout 'OK!' # Test gui publishing on guiland/one diff --git a/gno.land/pkg/integration/testdata/adduserfrom.txtar b/gno.land/pkg/integration/testdata/adduserfrom.txtar index 47ec70b00e6..8bbfaa738fd 100644 --- a/gno.land/pkg/integration/testdata/adduserfrom.txtar +++ b/gno.land/pkg/integration/testdata/adduserfrom.txtar @@ -14,13 +14,13 @@ stdout 'g1mtmrdmqfu0aryqfl4aw65n35haw2wdjkh5p4cp' gnoland start ## check users initial balance -gnokey query bank/balances/${USER_ADDR_user1} +gnokey query bank/balances/$user1_user_addr stdout '10000000ugnot' gnokey query bank/balances/g18e22n23g462drp4pyszyl6e6mwxkaylthgeeq4 stdout '10000000ugnot' -gnokey query auth/accounts/${USER_ADDR_user3} +gnokey query auth/accounts/$user3_user_addr stdout 'height: 0' stdout 'data: {' stdout ' "BaseAccount": {' diff --git a/gno.land/pkg/integration/testdata/event_multi_msg.txtar b/gno.land/pkg/integration/testdata/event_multi_msg.txtar index 4c8de856f03..3c5667b73b0 100644 --- a/gno.land/pkg/integration/testdata/event_multi_msg.txtar +++ b/gno.land/pkg/integration/testdata/event_multi_msg.txtar @@ -13,18 +13,18 @@ gnokey query auth/accounts/g1c0j899h88nwyvnzvh5jagpq6fkkyuj76nld6t0 stdout 'height: 0' stdout 'data: {' stdout ' "BaseAccount": {' -stdout ' "address": "g1c0j899h88nwyvnzvh5jagpq6fkkyuj76nld6t0",' +stdout ' "address": "'${user1_user_addr}'",' stdout ' "coins": "[0-9]*ugnot",' # dynamic stdout ' "public_key": null,' -stdout ' "account_number": "57",' -stdout ' "sequence": "0"' +stdout ' "account_number": "'${user1_account_num}'",' +stdout ' "sequence": "'${user1_account_seq}'"' stdout ' }' stdout '}' ! stderr '.+' # empty ## sign -gnokey sign -tx-path $WORK/multi/multi_msg.tx -chainid=tendermint_test -account-number 57 -account-sequence 0 user1 +gnokey sign -tx-path $WORK/multi/multi_msg.tx -chainid=tendermint_test -account-number $user1_account_num -account-sequence $user1_account_seq user1 stdout 'Tx successfully signed and saved to ' ## broadcast @@ -51,4 +51,3 @@ func Event(value string) { -- multi/multi_msg.tx -- {"msg":[{"@type":"/vm.m_call","caller":"g1c0j899h88nwyvnzvh5jagpq6fkkyuj76nld6t0","send":"","pkg_path":"gno.land/r/demo/simple_event","func":"Event","args":["value11"]},{"@type":"/vm.m_call","caller":"g1c0j899h88nwyvnzvh5jagpq6fkkyuj76nld6t0","send":"","pkg_path":"gno.land/r/demo/simple_event","func":"Event","args":["value22"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":null,"memo":""} - diff --git a/gno.land/pkg/integration/testdata/gnokey.txtar b/gno.land/pkg/integration/testdata/gnokey.txtar index 35759fa25dd..3268782b1ca 100644 --- a/gno.land/pkg/integration/testdata/gnokey.txtar +++ b/gno.land/pkg/integration/testdata/gnokey.txtar @@ -2,22 +2,21 @@ # golden files have been generated using UPDATE_SCRIPTS=true # add a random user -adduserfrom user1 'alpha ability feed thrive color fee grace message chief helmet laundry inmate index brave luxury toddler spawn vague index able zone shoe collect escape' -stdout 'g16v6rp3f4vehjspcu0g0xwz9xvehdkac9kslk5m' +adduser user1 # start gnoland gnoland start ## test1 account should be available on default -gnokey query auth/accounts/g16v6rp3f4vehjspcu0g0xwz9xvehdkac9kslk5m +gnokey query auth/accounts/$user1_user_addr stdout 'height: 0' stdout 'data: {' stdout ' "BaseAccount": {' -stdout ' "address": "g16v6rp3f4vehjspcu0g0xwz9xvehdkac9kslk5m",' +stdout ' "address": "'${user1_user_addr}'",' stdout ' "coins": "[0-9]*ugnot",' # dynamic stdout ' "public_key": null,' -stdout ' "account_number": "57",' -stdout ' "sequence": "0"' +stdout ' "account_number": "'${user1_account_num}'",' +stdout ' "sequence": "'${user1_account_seq}'"' stdout ' }' stdout '}' ! stderr '.+' # empty diff --git a/gno.land/pkg/integration/testdata/gnokey_simulate.txtar b/gno.land/pkg/integration/testdata/gnokey_simulate.txtar index db3cd527eb3..31b2249f8bb 100644 --- a/gno.land/pkg/integration/testdata/gnokey_simulate.txtar +++ b/gno.land/pkg/integration/testdata/gnokey_simulate.txtar @@ -6,41 +6,41 @@ loadpkg gno.land/r/hello $WORK/hello gnoland start # Initial state: assert that sequence == 0. -gnokey query auth/accounts/$USER_ADDR_test1 +gnokey query auth/accounts/$test1_user_addr stdout '"sequence": "1"' # attempt adding the "test" package. # the package has a syntax error; simulation should catch this ahead of time and prevent the tx. # -simulate test ! gnokey maketx addpkg -pkgdir $WORK/test -pkgpath gno.land/r/test -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test -simulate test test1 -gnokey query auth/accounts/$USER_ADDR_test1 +gnokey query auth/accounts/$test1_user_addr stdout '"sequence": "1"' # -simulate only ! gnokey maketx addpkg -pkgdir $WORK/test -pkgpath gno.land/r/test -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test -simulate only test1 -gnokey query auth/accounts/$USER_ADDR_test1 +gnokey query auth/accounts/$test1_user_addr stdout '"sequence": "1"' # -simulate skip ! gnokey maketx addpkg -pkgdir $WORK/test -pkgpath gno.land/r/test -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test -simulate skip test1 -gnokey query auth/accounts/$USER_ADDR_test1 +gnokey query auth/accounts/$test1_user_addr stdout '"sequence": "2"' # attempt calling hello.SetName correctly. # -simulate test and skip should do it successfully, -simulate only should not. # -simulate test gnokey maketx call -pkgpath gno.land/r/hello -func SetName -args John -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate test test1 -gnokey query auth/accounts/$USER_ADDR_test1 +gnokey query auth/accounts/$test1_user_addr stdout '"sequence": "3"' gnokey query vm/qeval --data "gno.land/r/hello.Hello()" stdout 'Hello, John!' # -simulate only gnokey maketx call -pkgpath gno.land/r/hello -func SetName -args Paul -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate only test1 -gnokey query auth/accounts/$USER_ADDR_test1 +gnokey query auth/accounts/$test1_user_addr stdout '"sequence": "3"' gnokey query vm/qeval --data "gno.land/r/hello.Hello()" stdout 'Hello, John!' # -simulate skip gnokey maketx call -pkgpath gno.land/r/hello -func SetName -args George -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate skip test1 -gnokey query auth/accounts/$USER_ADDR_test1 +gnokey query auth/accounts/$test1_user_addr stdout '"sequence": "4"' gnokey query vm/qeval --data "gno.land/r/hello.Hello()" stdout 'Hello, George!' @@ -50,19 +50,19 @@ stdout 'Hello, George!' # none should change the name (ie. panic rollbacks). # -simulate test ! gnokey maketx call -pkgpath gno.land/r/hello -func Grumpy -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate test test1 -gnokey query auth/accounts/$USER_ADDR_test1 +gnokey query auth/accounts/$test1_user_addr stdout '"sequence": "4"' gnokey query vm/qeval --data "gno.land/r/hello.Hello()" stdout 'Hello, George!' # -simulate only ! gnokey maketx call -pkgpath gno.land/r/hello -func Grumpy -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate only test1 -gnokey query auth/accounts/$USER_ADDR_test1 +gnokey query auth/accounts/$test1_user_addr stdout '"sequence": "4"' gnokey query vm/qeval --data "gno.land/r/hello.Hello()" stdout 'Hello, George!' # -simulate skip ! gnokey maketx call -pkgpath gno.land/r/hello -func Grumpy -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate skip test1 -gnokey query auth/accounts/$USER_ADDR_test1 +gnokey query auth/accounts/$test1_user_addr stdout '"sequence": "5"' gnokey query vm/qeval --data "gno.land/r/hello.Hello()" stdout 'Hello, George!' diff --git a/gno.land/pkg/integration/testdata/patchpkg.txtar b/gno.land/pkg/integration/testdata/patchpkg.txtar index c5962709625..0a1a7fa993d 100644 --- a/gno.land/pkg/integration/testdata/patchpkg.txtar +++ b/gno.land/pkg/integration/testdata/patchpkg.txtar @@ -2,13 +2,13 @@ loadpkg gno.land/r/dev/admin $WORK adduser dev -patchpkg "g1abcde" $USER_ADDR_dev +patchpkg "g1abcde" $dev_user_addr gnoland start gnokey maketx call -pkgpath gno.land/r/dev/admin -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1 ! stdout g1abcde -stdout $USER_ADDR_dev +stdout $dev_user_addr -- admin.gno -- package admin diff --git a/gno.land/pkg/integration/testdata/prevrealm.txtar b/gno.land/pkg/integration/testdata/prevrealm.txtar index 20317d87345..4bbe16c3205 100644 --- a/gno.land/pkg/integration/testdata/prevrealm.txtar +++ b/gno.land/pkg/integration/testdata/prevrealm.txtar @@ -30,65 +30,65 @@ loadpkg gno.land/p/demo/bar $WORK/p/demo/bar ## start a new node gnoland start -env RFOO_ADDR=g1evezrh92xaucffmtgsaa3rvmz5s8kedffsg469 +env RFOO_USER_ADDR=g1evezrh92xaucffmtgsaa3rvmz5s8kedffsg469 # Test cases ## 1. MsgCall -> myrlm.A: user address gnokey maketx call -pkgpath gno.land/r/myrlm -func A -gas-fee 100000ugnot -gas-wanted 700000 -broadcast -chainid tendermint_test test1 -stdout ${USER_ADDR_test1} +stdout ${test1_user_addr} ## 2. MsgCall -> myrealm.B -> myrlm.A: user address gnokey maketx call -pkgpath gno.land/r/myrlm -func B -gas-fee 100000ugnot -gas-wanted 800000 -broadcast -chainid tendermint_test test1 -stdout ${USER_ADDR_test1} +stdout ${test1_user_addr} ## 3. MsgCall -> r/foo.A -> myrlm.A: r/foo gnokey maketx call -pkgpath gno.land/r/foo -func A -gas-fee 100000ugnot -gas-wanted 800000 -broadcast -chainid tendermint_test test1 -stdout ${RFOO_ADDR} +stdout ${RFOO_USER_ADDR} ## 4. MsgCall -> r/foo.B -> myrlm.B -> r/foo.A: r/foo gnokey maketx call -pkgpath gno.land/r/foo -func B -gas-fee 100000ugnot -gas-wanted 800000 -broadcast -chainid tendermint_test test1 -stdout ${RFOO_ADDR} +stdout ${RFOO_USER_ADDR} ## remove due to update to maketx call can only call realm (case 5, 6, 13) ## 5. MsgCall -> p/demo/bar.A: user address ## gnokey maketx call -pkgpath gno.land/p/demo/bar -func A -gas-fee 100000ugnot -gas-wanted 4000000 -broadcast -chainid tendermint_test test1 -## stdout ${USER_ADDR_test1} +## stdout ${test1_user_addr} ## 6. MsgCall -> p/demo/bar.B: user address ## gnokey maketx call -pkgpath gno.land/p/demo/bar -func B -gas-fee 100000ugnot -gas-wanted 4000000 -broadcast -chainid tendermint_test test1 -## stdout ${USER_ADDR_test1} +## stdout ${test1_user_addr} ## 7. MsgRun -> myrlm.A: user address gnokey maketx run -gas-fee 100000ugnot -gas-wanted 12000000 -broadcast -chainid tendermint_test test1 $WORK/run/myrlmA.gno -stdout ${USER_ADDR_test1} +stdout ${test1_user_addr} ## 8. MsgRun -> myrealm.B -> myrlm.A: user address gnokey maketx run -gas-fee 100000ugnot -gas-wanted 12000000 -broadcast -chainid tendermint_test test1 $WORK/run/myrlmB.gno -stdout ${USER_ADDR_test1} +stdout ${test1_user_addr} ## 9. MsgRun -> r/foo.A -> myrlm.A: r/foo gnokey maketx run -gas-fee 100000ugnot -gas-wanted 12000000 -broadcast -chainid tendermint_test test1 $WORK/run/fooA.gno -stdout ${RFOO_ADDR} +stdout ${RFOO_USER_ADDR} ## 10. MsgRun -> r/foo.B -> myrlm.B -> r/foo.A: r/foo gnokey maketx run -gas-fee 100000ugnot -gas-wanted 12000000 -broadcast -chainid tendermint_test test1 $WORK/run/fooB.gno -stdout ${RFOO_ADDR} +stdout ${RFOO_USER_ADDR} ## 11. MsgRun -> p/demo/bar.A -> myrlm.A: user address gnokey maketx run -gas-fee 100000ugnot -gas-wanted 12000000 -broadcast -chainid tendermint_test test1 $WORK/run/barA.gno -stdout ${USER_ADDR_test1} +stdout ${test1_user_addr} ## 12. MsgRun -> p/demo/bar.B -> myrlm.B -> r/foo.A: user address gnokey maketx run -gas-fee 100000ugnot -gas-wanted 12000000 -broadcast -chainid tendermint_test test1 $WORK/run/barB.gno -stdout ${USER_ADDR_test1} +stdout ${test1_user_addr} ## 13. MsgCall -> std.PrevRealm(): user address ## gnokey maketx call -pkgpath std -func PrevRealm -gas-fee 100000ugnot -gas-wanted 4000000 -broadcast -chainid tendermint_test test1 -## stdout ${USER_ADDR_test1} +## stdout ${test1_user_addr} ## 14. MsgRun -> std.PrevRealm(): user address gnokey maketx run -gas-fee 100000ugnot -gas-wanted 12000000 -broadcast -chainid tendermint_test test1 $WORK/run/baz.gno -stdout ${USER_ADDR_test1} +stdout ${test1_user_addr} -- r/myrlm/myrlm.gno -- package myrlm diff --git a/gno.land/pkg/integration/testdata/realm_banker_issued_coin_denom.txtar b/gno.land/pkg/integration/testdata/realm_banker_issued_coin_denom.txtar index be9a686bac6..a55604267ae 100644 --- a/gno.land/pkg/integration/testdata/realm_banker_issued_coin_denom.txtar +++ b/gno.land/pkg/integration/testdata/realm_banker_issued_coin_denom.txtar @@ -19,31 +19,31 @@ gnokey maketx addpkg -pkgdir $WORK/invalid_realm_denom -pkgpath gno.land/r/test/ gnokey maketx send -send "9999999ugnot" -to g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 -gas-fee 1ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test2 ## check test2 balance -gnokey query bank/balances/${USER_ADDR_test2} +gnokey query bank/balances/${test2_user_addr} stdout '' ## mint coin from banker -gnokey maketx call -pkgpath gno.land/r/test/realm_banker -func Mint -args ${USER_ADDR_test2} -args "ugnot" -args "31337" -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/test/realm_banker -func Mint -args ${test2_user_addr} -args "ugnot" -args "31337" -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 ## check balance after minting, without patching banker will return '31337ugnot' -gnokey query bank/balances/${USER_ADDR_test2} +gnokey query bank/balances/${test2_user_addr} stdout '"31337/gno.land/r/test/realm_banker:ugnot"' ## burn coin -gnokey maketx call -pkgpath gno.land/r/test/realm_banker -func Burn -args ${USER_ADDR_test2} -args "ugnot" -args "7" -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/test/realm_banker -func Burn -args ${test2_user_addr} -args "ugnot" -args "7" -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 ## check balance after burning -gnokey query bank/balances/${USER_ADDR_test2} +gnokey query bank/balances/${test2_user_addr} stdout '"31330/gno.land/r/test/realm_banker:ugnot"' ## transfer 1ugnot to test2 for gas-fee of below tx -gnokey maketx send -send "1ugnot" -to ${USER_ADDR_test2} -gas-fee 1ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx send -send "1ugnot" -to ${test2_user_addr} -gas-fee 1ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 ## transfer coin gnokey maketx send -send "1330/gno.land/r/test/realm_banker:ugnot" -to g1yr0dpfgthph7y6mepdx8afuec4q3ga2lg8tjt0 -gas-fee 1ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test2 ## check sender balance -gnokey query bank/balances/${USER_ADDR_test2} +gnokey query bank/balances/${test2_user_addr} stdout '"30000/gno.land/r/test/realm_banker:ugnot"' ## check receiver balance @@ -121,4 +121,4 @@ func Mint(addr std.Address, denom string, amount int64) { func Burn(addr std.Address, denom string, amount int64) { banker := std.GetBanker(std.BankerTypeRealmIssue) banker.RemoveCoin(addr, denom, amount) -} \ No newline at end of file +} diff --git a/gno.land/pkg/integration/testscript_gnoland.go b/gno.land/pkg/integration/testscript_gnoland.go index 9781799ea7d..1531b83dfef 100644 --- a/gno.land/pkg/integration/testscript_gnoland.go +++ b/gno.land/pkg/integration/testscript_gnoland.go @@ -20,6 +20,9 @@ import ( "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" "github.com/gnolang/gno/gno.land/pkg/keyscli" "github.com/gnolang/gno/gnovm/pkg/gnoenv" + "github.com/gnolang/gno/tm2/pkg/amino" + rpcclient "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" + ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" bft "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/crypto" @@ -45,6 +48,7 @@ const ( envKeyPrivValKey envKeyExecCommand envKeyExecBin + envKeyBase ) type commandkind int @@ -158,13 +162,18 @@ func SetupGnolandTestscript(t *testing.T, p *testscript.Params) error { } kb.ImportPrivKey(DefaultAccount_Name, defaultPK, "") - env.Setenv("USER_SEED_"+DefaultAccount_Name, DefaultAccount_Seed) - env.Setenv("USER_ADDR_"+DefaultAccount_Name, DefaultAccount_Address) + env.Setenv(DefaultAccount_Name+"_user_seed", DefaultAccount_Seed) + env.Setenv(DefaultAccount_Name+"_user_addr", DefaultAccount_Address) // New private key env.Values[envKeyPrivValKey] = ed25519.GenPrivKey() + + // Set gno dbdir env.Setenv("GNO_DBDIR", dbdir) + // Setup account store + env.Values[envKeyBase] = kb + // Generate node short id var sid string { @@ -215,6 +224,7 @@ func SetupGnolandTestscript(t *testing.T, p *testscript.Params) error { "adduserfrom": adduserfromCmd(nodesManager), "patchpkg": patchpkgCmd(), "loadpkg": loadpkgCmd(gnoRootDir), + "scanf": loadpkgCmd(gnoRootDir), } // Initialize cmds map if needed @@ -305,8 +315,11 @@ func gnolandCmd(t *testing.T, nodesManager *NodesManager, gnoRootDir string) fun }) nodesManager.Set(sid, &tNodeProcess{NodeProcess: nodep, cfg: cfg}) - ts.Setenv("RPC_ADDR", nodep.Address()) + + // Load user infos + loadUserEnv(ts, nodep.Address()) + fmt.Fprintln(ts.Stdout(), "node started successfully") case "restart": @@ -337,6 +350,9 @@ func gnolandCmd(t *testing.T, nodesManager *NodesManager, gnoRootDir string) fun ts.Setenv("RPC_ADDR", nodep.Address()) nodesManager.Set(sid, &tNodeProcess{NodeProcess: nodep, cfg: node.cfg}) + // Load user infos + loadUserEnv(ts, nodep.Address()) + fmt.Fprintln(ts.Stdout(), "node restarted successfully") case "stop": @@ -534,6 +550,64 @@ func loadpkgCmd(gnoRootDir string) func(ts *testscript.TestScript, neg bool, arg } } +func loadUserEnv(ts *testscript.TestScript, remote string) error { + const path = "auth/accounts" + + // List all accounts + kb := ts.Value(envKeyBase).(keys.Keybase) + accounts, err := kb.List() + if err != nil { + ts.Fatalf("query accounts: unable to list keys: %s", err) + } + + cli, err := rpcclient.NewHTTPClient(remote) + if err != nil { + return fmt.Errorf("unable create rpc client %q: %w", remote, err) + } + + batch := cli.NewBatch() + for _, account := range accounts { + accountPath := filepath.Join(path, account.GetAddress().String()) + if err := batch.ABCIQuery(accountPath, []byte{}); err != nil { + return fmt.Errorf("unable to create query request: %w", err) + } + } + + batchRes, err := batch.Send(context.Background()) + if err != nil { + return fmt.Errorf("unable to query accounts: %w", err) + } + + if len(batchRes) != len(accounts) { + ts.Fatalf("query accounts: len(res) != len(accounts)") + } + + for i, res := range batchRes { + account := accounts[i] + name := account.GetName() + qres := res.(*ctypes.ResultABCIQuery) + + if err := qres.Response.Error; err != nil { + ts.Fatalf("query account %q error: %s", account.GetName(), err.Error()) + } + + var qret struct{ BaseAccount std.BaseAccount } + if err = amino.UnmarshalJSON(qres.Response.Data, &qret); err != nil { + ts.Fatalf("query account %q unarmshal error: %s", account.GetName(), err.Error()) + } + + strAccountNumber := strconv.Itoa(int(qret.BaseAccount.GetAccountNumber())) + ts.Setenv(name+"_account_num", strAccountNumber) + ts.Logf("[%q] account number: %s", name, strAccountNumber) + + strAccountSequence := strconv.Itoa(int(qret.BaseAccount.GetSequence())) + ts.Setenv(name+"_account_seq", strAccountSequence) + ts.Logf("[%q] account sequence: %s", name, strAccountNumber) + } + + return nil +} + type tsLogWriter struct { ts *testscript.TestScript } @@ -589,94 +663,8 @@ func setupNode(ts *testscript.TestScript, ctx context.Context, cfg *ProcessNodeC return nil } -// `unquote` takes a slice of strings, resulting from splitting a string block by spaces, and -// processes them. The function handles quoted phrases and escape characters within these strings. -func unquote(args []string) ([]string, error) { - const quote = '"' - - parts := []string{} - var inQuote bool - - var part strings.Builder - for _, arg := range args { - var escaped bool - for _, c := range arg { - if escaped { - // If the character is meant to be escaped, it is processed with Unquote. - // We use `Unquote` here for two main reasons: - // 1. It will validate that the escape sequence is correct - // 2. It converts the escaped string to its corresponding raw character. - // For example, "\\t" becomes '\t'. - uc, err := strconv.Unquote(`"\` + string(c) + `"`) - if err != nil { - return nil, fmt.Errorf("unhandled escape sequence `\\%c`: %w", c, err) - } - - part.WriteString(uc) - escaped = false - continue - } - - // If we are inside a quoted string and encounter an escape character, - // flag the next character as `escaped` - if inQuote && c == '\\' { - escaped = true - continue - } - - // Detect quote and toggle inQuote state - if c == quote { - inQuote = !inQuote - continue - } - - // Handle regular character - part.WriteRune(c) - } - - // If we're inside a quote, add a single space. - // It reflects one or multiple spaces between args in the original string. - if inQuote { - part.WriteRune(' ') - continue - } - - // Finalize part, add to parts, and reset for next part - parts = append(parts, part.String()) - part.Reset() - } - - // Check if a quote is left open - if inQuote { - return nil, errors.New("unfinished quote") - } - - return parts, nil -} - -func getNodeSID(ts *testscript.TestScript) string { - return ts.Getenv("SID") -} - -func tsValidateError(ts *testscript.TestScript, cmd string, neg bool, err error) { - if err != nil { - fmt.Fprintf(ts.Stderr(), "%q error: %+v\n", cmd, err) - if !neg { - ts.Fatalf("unexpected %q command failure: %s", cmd, err) - } - } else { - if neg { - ts.Fatalf("unexpected %q command success", cmd) - } - } -} - -type envSetter interface { - Setenv(key, value string) -} - // createAccount creates a new account with the given name and adds it to the keybase. -func createAccount(env envSetter, kb keys.Keybase, accountName string) (gnoland.Balance, error) { +func createAccount(ts *testscript.TestScript, kb keys.Keybase, accountName string) (gnoland.Balance, error) { var balance gnoland.Balance entropy, err := bip39.NewEntropy(256) if err != nil { @@ -688,23 +676,11 @@ func createAccount(env envSetter, kb keys.Keybase, accountName string) (gnoland. return balance, fmt.Errorf("error generating mnemonic: %w", err) } - var keyInfo keys.Info - if keyInfo, err = kb.CreateAccount(accountName, mnemonic, "", "", 0, 0); err != nil { - return balance, fmt.Errorf("unable to create account: %w", err) - } - - address := keyInfo.GetAddress() - env.Setenv("USER_SEED_"+accountName, mnemonic) - env.Setenv("USER_ADDR_"+accountName, address.String()) - - return gnoland.Balance{ - Address: address, - Amount: std.Coins{std.NewCoin(ugnot.Denom, 10e6)}, - }, nil + return createAccountFrom(ts, kb, accountName, mnemonic, 0, 0) } // createAccountFrom creates a new account with the given metadata and adds it to the keybase. -func createAccountFrom(env envSetter, kb keys.Keybase, accountName, mnemonic string, account, index uint32) (gnoland.Balance, error) { +func createAccountFrom(ts *testscript.TestScript, kb keys.Keybase, accountName, mnemonic string, account, index uint32) (gnoland.Balance, error) { var balance gnoland.Balance // check if mnemonic is valid @@ -718,8 +694,8 @@ func createAccountFrom(env envSetter, kb keys.Keybase, accountName, mnemonic str } address := keyInfo.GetAddress() - env.Setenv("USER_SEED_"+accountName, mnemonic) - env.Setenv("USER_ADDR_"+accountName, address.String()) + ts.Setenv(accountName+"_user_seed", mnemonic) + ts.Setenv(accountName+"_user_addr", address.String()) return gnoland.Balance{ Address: address, @@ -787,3 +763,20 @@ func GeneratePrivKeyFromMnemonic(mnemonic, bip39Passphrase string, account, inde privKey := secp256k1.PrivKeySecp256k1(derivedPriv) return privKey, nil } + +func getNodeSID(ts *testscript.TestScript) string { + return ts.Getenv("SID") +} + +func tsValidateError(ts *testscript.TestScript, cmd string, neg bool, err error) { + if err != nil { + fmt.Fprintf(ts.Stderr(), "%q error: %+v\n", cmd, err) + if !neg { + ts.Fatalf("unexpected %q command failure: %s", cmd, err) + } + } else { + if neg { + ts.Fatalf("unexpected %q command success", cmd) + } + } +} diff --git a/gno.land/pkg/integration/utils.go b/gno.land/pkg/integration/utils.go new file mode 100644 index 00000000000..bc9e7f1e220 --- /dev/null +++ b/gno.land/pkg/integration/utils.go @@ -0,0 +1,73 @@ +package integration + +import ( + "errors" + "fmt" + "strconv" + "strings" +) + +// `unquote` takes a slice of strings, resulting from splitting a string block by spaces, and +// processes them. The function handles quoted phrases and escape characters within these strings. +func unquote(args []string) ([]string, error) { + const quote = '"' + + parts := []string{} + var inQuote bool + + var part strings.Builder + for _, arg := range args { + var escaped bool + for _, c := range arg { + if escaped { + // If the character is meant to be escaped, it is processed with Unquote. + // We use `Unquote` here for two main reasons: + // 1. It will validate that the escape sequence is correct + // 2. It converts the escaped string to its corresponding raw character. + // For example, "\\t" becomes '\t'. + uc, err := strconv.Unquote(`"\` + string(c) + `"`) + if err != nil { + return nil, fmt.Errorf("unhandled escape sequence `\\%c`: %w", c, err) + } + + part.WriteString(uc) + escaped = false + continue + } + + // If we are inside a quoted string and encounter an escape character, + // flag the next character as `escaped` + if inQuote && c == '\\' { + escaped = true + continue + } + + // Detect quote and toggle inQuote state + if c == quote { + inQuote = !inQuote + continue + } + + // Handle regular character + part.WriteRune(c) + } + + // If we're inside a quote, add a single space. + // It reflects one or multiple spaces between args in the original string. + if inQuote { + part.WriteRune(' ') + continue + } + + // Finalize part, add to parts, and reset for next part + parts = append(parts, part.String()) + part.Reset() + } + + // Check if a quote is left open + if inQuote { + return nil, errors.New("unfinished quote") + } + + return parts, nil +} diff --git a/gno.land/pkg/integration/testscript_gnoland_test.go b/gno.land/pkg/integration/utils_test.go similarity index 100% rename from gno.land/pkg/integration/testscript_gnoland_test.go rename to gno.land/pkg/integration/utils_test.go From ddfff6096fc96631f15a9fae1bd6f72e15d40e13 Mon Sep 17 00:00:00 2001 From: Morgan Date: Mon, 13 Jan 2025 11:35:05 +0100 Subject: [PATCH 02/20] chore(gnovm): remove unused attributes (#3492) --- gnovm/pkg/gnolang/nodes.go | 6 ++---- gnovm/pkg/gnolang/preprocess.go | 12 +----------- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go index 0496d37ed72..b85d1ac7026 100644 --- a/gnovm/pkg/gnolang/nodes.go +++ b/gnovm/pkg/gnolang/nodes.go @@ -153,10 +153,8 @@ const ( ATTR_TYPE_VALUE GnoAttribute = "ATTR_TYPE_VALUE" ATTR_TYPEOF_VALUE GnoAttribute = "ATTR_TYPEOF_VALUE" ATTR_IOTA GnoAttribute = "ATTR_IOTA" - ATTR_LOCATIONED GnoAttribute = "ATTR_LOCATIONE" // XXX DELETE - ATTR_GOTOLOOP_STMT GnoAttribute = "ATTR_GOTOLOOP_STMT" // XXX delete? - ATTR_LOOP_DEFINES GnoAttribute = "ATTR_LOOP_DEFINES" // []Name defined within loops. - ATTR_LOOP_USES GnoAttribute = "ATTR_LOOP_USES" // []Name loop defines actually used. + ATTR_LOOP_DEFINES GnoAttribute = "ATTR_LOOP_DEFINES" // []Name defined within loops. + ATTR_LOOP_USES GnoAttribute = "ATTR_LOOP_USES" // []Name loop defines actually used. ATTR_SHIFT_RHS GnoAttribute = "ATTR_SHIFT_RHS" ATTR_LAST_BLOCK_STMT GnoAttribute = "ATTR_LAST_BLOCK_STMT" ) diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 79695d8888a..ddfd1851989 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -2723,17 +2723,10 @@ func findGotoLoopDefines(ctx BlockNode, bn BlockNode) { // Otherwise mark stmt as gotoloop. case Stmt: // we're done if we - // re-encounter origGotoStmtm. + // re-encounter origGotoStmt. if n == origGoto { - n.SetAttribute( - ATTR_GOTOLOOP_STMT, - true) return n, TRANS_EXIT // done } - // otherwise set attribute. - n.SetAttribute( - ATTR_GOTOLOOP_STMT, - true) return n, TRANS_CONTINUE // Special case, maybe convert // NameExprTypeDefine to @@ -4804,9 +4797,6 @@ func setNodeLines(n Node) { // based on sparse expectations on block nodes, and ensures uniqueness of BlockNode.Locations. // Ensures uniqueness of BlockNode.Locations. func setNodeLocations(pkgPath string, fileName string, n Node) { - if n.GetAttribute(ATTR_LOCATIONED) == true { - return // locations already set (typically n is a filenode). - } if pkgPath == "" || fileName == "" { panic("missing package path or file name") } From 663edac6246046f6cac0af912b0c38722f5eaa66 Mon Sep 17 00:00:00 2001 From: 6h057 <15034695+omarsy@users.noreply.github.com> Date: Mon, 13 Jan 2025 12:11:33 +0100 Subject: [PATCH 03/20] feat(gnovm): add software floating point package (#3185) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes: #312 The idea to use softfloat and work originates from this PR: https://github.com/gnolang/gno/pull/2863
Contributors' checklist... - [ ] Added new tests, or not needed, or not feasible - [ ] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [ ] Updated the official documentation or not needed - [ ] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [ ] Added references to related issues and PRs - [ ] Provided any useful hints for running manual tests
--------- Co-authored-by: Morgan Bazalgette Co-authored-by: Miloš Živković --- gno.land/pkg/sdk/vm/convert.go | 5 +- gnovm/pkg/gnolang/frame.go | 5 +- gnovm/pkg/gnolang/gonative.go | 19 +- gnovm/pkg/gnolang/internal/softfloat/copy.sh | 32 + .../internal/softfloat/runtime_softfloat64.go | 631 ++++++++++++++++++ .../softfloat/runtime_softfloat64_test.go | 204 ++++++ .../gnolang/internal/softfloat/softfloat.go | 134 ++++ gnovm/pkg/gnolang/op_binary.go | 46 +- gnovm/pkg/gnolang/op_inc_dec.go | 9 +- gnovm/pkg/gnolang/op_unary.go | 5 +- gnovm/pkg/gnolang/values.go | 21 +- gnovm/pkg/gnolang/values_conversions.go | 258 +++---- gnovm/pkg/gnolang/values_conversions_test.go | 3 +- gnovm/pkg/gnolang/values_string.go | 9 +- gnovm/tests/files/float8.gno | 166 +++++ 15 files changed, 1360 insertions(+), 187 deletions(-) create mode 100644 gnovm/pkg/gnolang/internal/softfloat/copy.sh create mode 100644 gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64.go create mode 100644 gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64_test.go create mode 100644 gnovm/pkg/gnolang/internal/softfloat/softfloat.go create mode 100644 gnovm/tests/files/float8.gno diff --git a/gno.land/pkg/sdk/vm/convert.go b/gno.land/pkg/sdk/vm/convert.go index cafb6cad67f..dbaabcfbc4b 100644 --- a/gno.land/pkg/sdk/vm/convert.go +++ b/gno.land/pkg/sdk/vm/convert.go @@ -3,6 +3,7 @@ package vm import ( "encoding/base64" "fmt" + "math" "strconv" "strings" @@ -143,11 +144,11 @@ func convertArgToGno(arg string, argT gno.Type) (tv gno.TypedValue) { return case gno.Float32Type: value := convertFloat(arg, 32) - tv.SetFloat32(float32(value)) + tv.SetFloat32(math.Float32bits(float32(value))) return case gno.Float64Type: value := convertFloat(arg, 64) - tv.SetFloat64(value) + tv.SetFloat64(math.Float64bits(value)) return default: panic(fmt.Sprintf("unexpected primitive type %s", bt.String())) diff --git a/gnovm/pkg/gnolang/frame.go b/gnovm/pkg/gnolang/frame.go index 2ac1027eb32..60f19979b7a 100644 --- a/gnovm/pkg/gnolang/frame.go +++ b/gnovm/pkg/gnolang/frame.go @@ -2,6 +2,7 @@ package gnolang import ( "fmt" + "math" "strings" ) @@ -207,9 +208,9 @@ func toConstExpTrace(cte *ConstExpr) string { case Uint64Type: return fmt.Sprintf("%d", tv.GetUint64()) case Float32Type: - return fmt.Sprintf("%v", tv.GetFloat32()) + return fmt.Sprintf("%v", math.Float32frombits(tv.GetFloat32())) case Float64Type: - return fmt.Sprintf("%v", tv.GetFloat64()) + return fmt.Sprintf("%v", math.Float64frombits(tv.GetFloat64())) } } diff --git a/gnovm/pkg/gnolang/gonative.go b/gnovm/pkg/gnolang/gonative.go index 5a39c76b5e1..85fc8b70051 100644 --- a/gnovm/pkg/gnolang/gonative.go +++ b/gnovm/pkg/gnolang/gonative.go @@ -2,7 +2,10 @@ package gnolang import ( "fmt" + "math" "reflect" + + "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat" ) // NOTE @@ -329,9 +332,9 @@ func go2GnoValue(alloc *Allocator, rv reflect.Value) (tv TypedValue) { case reflect.Uint64: tv.SetUint64(rv.Uint()) case reflect.Float32: - tv.SetFloat32(float32(rv.Float())) + tv.SetFloat32(softfloat.F64to32(math.Float64bits(rv.Float()))) case reflect.Float64: - tv.SetFloat64(rv.Float()) + tv.SetFloat64(math.Float64bits(rv.Float())) case reflect.Array: tv.V = alloc.NewNative(rv) case reflect.Slice: @@ -428,11 +431,11 @@ func go2GnoValueUpdate(alloc *Allocator, rlm *Realm, lvl int, tv *TypedValue, rv } case Float32Kind: if lvl != 0 { - tv.SetFloat32(float32(rv.Float())) + tv.SetFloat32(softfloat.F64to32(math.Float64bits(rv.Float()))) } case Float64Kind: if lvl != 0 { - tv.SetFloat64(rv.Float()) + tv.SetFloat64(math.Float64bits(rv.Float())) } case BigintKind: panic("not yet implemented") @@ -644,9 +647,9 @@ func go2GnoValue2(alloc *Allocator, store Store, rv reflect.Value, recursive boo case reflect.Uint64: tv.SetUint64(rv.Uint()) case reflect.Float32: - tv.SetFloat32(float32(rv.Float())) + tv.SetFloat32(softfloat.F64to32(math.Float64bits(rv.Float()))) case reflect.Float64: - tv.SetFloat64(rv.Float()) + tv.SetFloat64(math.Float64bits(rv.Float())) case reflect.Array: rvl := rv.Len() if rv.Type().Elem().Kind() == reflect.Uint8 { @@ -1049,9 +1052,9 @@ func gno2GoValue(tv *TypedValue, rv reflect.Value) (ret reflect.Value) { case Uint64Type: rv.SetUint(tv.GetUint64()) case Float32Type: - rv.SetFloat(float64(tv.GetFloat32())) + rv.SetFloat(math.Float64frombits(softfloat.F32to64(tv.GetFloat32()))) case Float64Type: - rv.SetFloat(tv.GetFloat64()) + rv.SetFloat(math.Float64frombits(tv.GetFloat64())) default: panic(fmt.Sprintf( "unexpected type %s", diff --git a/gnovm/pkg/gnolang/internal/softfloat/copy.sh b/gnovm/pkg/gnolang/internal/softfloat/copy.sh new file mode 100644 index 00000000000..6d2a8f80462 --- /dev/null +++ b/gnovm/pkg/gnolang/internal/softfloat/copy.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +# softfloat64.go: +# - add header +# - change package name +cat > runtime_softfloat64.go << EOF +// Code generated by copy.sh. DO NOT EDIT. +// This file is copied from \$GOROOT/src/runtime/softfloat64.go. +// It is the software floating point implementation used by the Go runtime. + +EOF +cat "$GOROOT/src/runtime/softfloat64.go" >> ./runtime_softfloat64.go +sed -i 's/^package runtime$/package softfloat/' runtime_softfloat64.go + +# softfloat64_test.go: +# - add header +# - change package name +# - change import to right package +# - change GOARCH to runtime.GOARCH, and import the "runtime" package +cat > runtime_softfloat64_test.go << EOF +// Code generated by copy.sh. DO NOT EDIT. +// This file is copied from \$GOROOT/src/runtime/softfloat64_test.go. +// It is the tests for the software floating point implementation +// used by the Go runtime. + +EOF +cat "$GOROOT/src/runtime/softfloat64_test.go" >> ./runtime_softfloat64_test.go +sed -i 's/^package runtime_test$/package softfloat_test/ +s#^\t\. "runtime"$#\t. "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat"# +s/GOARCH/runtime.GOARCH/g +16a\ + "runtime"' runtime_softfloat64_test.go \ No newline at end of file diff --git a/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64.go b/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64.go new file mode 100644 index 00000000000..cf2ad5afd8a --- /dev/null +++ b/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64.go @@ -0,0 +1,631 @@ +// Code generated by copy.sh. DO NOT EDIT. +// This file is copied from $GOROOT/src/runtime/softfloat64.go. +// It is the software floating point implementation used by the Go runtime. + +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Software IEEE754 64-bit floating point. +// Only referred to (and thus linked in) by softfloat targets +// and by tests in this directory. + +package softfloat + +const ( + mantbits64 uint = 52 + expbits64 uint = 11 + bias64 = -1<<(expbits64-1) + 1 + + nan64 uint64 = (1<>mantbits64) & (1<>mantbits32) & (1<= 4<>= 1 + exp++ + } + if mant >= 2<= 4<>= 1 + exp++ + } + } + mant >>= 1 + exp++ + } + if exp >= 1<>= 1 + exp++ + } + if mant&1 != 0 && (trunc != 0 || mant&2 != 0) { + mant++ + } + mant >>= 1 + exp++ + if mant < 1<= 4<>= 1 + exp++ + } + if mant >= 2<= 4<>= 1 + exp++ + } + } + mant >>= 1 + exp++ + } + if exp >= 1<>= 1 + exp++ + } + if mant&1 != 0 && (trunc != 0 || mant&2 != 0) { + mant++ + } + mant >>= 1 + exp++ + if mant < 1<>= shift + if fs == gs { + fm += gm + } else { + fm -= gm + if trunc != 0 { + fm-- + } + } + if fm == 0 { + fs = 0 + } + return fpack64(fs, fm, fe-2, trunc) +} + +func fsub64(f, g uint64) uint64 { + return fadd64(f, fneg64(g)) +} + +func fneg64(f uint64) uint64 { + return f ^ (1 << (mantbits64 + expbits64)) +} + +func fmul64(f, g uint64) uint64 { + fs, fm, fe, fi, fn := funpack64(f) + gs, gm, ge, gi, gn := funpack64(g) + + // Special cases. + switch { + case fn || gn: // NaN * g or f * NaN = NaN + return nan64 + + case fi && gi: // Inf * Inf = Inf (with sign adjusted) + return f ^ gs + + case fi && gm == 0, fm == 0 && gi: // 0 * Inf = Inf * 0 = NaN + return nan64 + + case fm == 0: // 0 * x = 0 (with sign adjusted) + return f ^ gs + + case gm == 0: // x * 0 = 0 (with sign adjusted) + return g ^ fs + } + + // 53-bit * 53-bit = 107- or 108-bit + lo, hi := mullu(fm, gm) + shift := mantbits64 - 1 + trunc := lo & (1<>shift + return fpack64(fs^gs, mant, fe+ge-1, trunc) +} + +func fdiv64(f, g uint64) uint64 { + fs, fm, fe, fi, fn := funpack64(f) + gs, gm, ge, gi, gn := funpack64(g) + + // Special cases. + switch { + case fn || gn: // NaN / g = f / NaN = NaN + return nan64 + + case fi && gi: // ±Inf / ±Inf = NaN + return nan64 + + case !fi && !gi && fm == 0 && gm == 0: // 0 / 0 = NaN + return nan64 + + case fi, !gi && gm == 0: // Inf / g = f / 0 = Inf + return fs ^ gs ^ inf64 + + case gi, fm == 0: // f / Inf = 0 / g = Inf + return fs ^ gs ^ 0 + } + _, _, _, _ = fi, fn, gi, gn + + // 53-bit<<54 / 53-bit = 53- or 54-bit. + shift := mantbits64 + 2 + q, r := divlu(fm>>(64-shift), fm<> 32) + if fi { + return fs32 ^ inf32 + } + const d = mantbits64 - mantbits32 - 1 + return fpack32(fs32, uint32(fm>>d), fe-1, uint32(fm&(1< gs: // f < 0, g > 0 + return -1, false + + case fs < gs: // f > 0, g < 0 + return +1, false + + // Same sign, not NaN. + // Can compare encodings directly now. + // Reverse for sign. + case fs == 0 && f < g, fs != 0 && f > g: + return -1, false + + case fs == 0 && f > g, fs != 0 && f < g: + return +1, false + } + + // f == g + return 0, false +} + +func f64toint(f uint64) (val int64, ok bool) { + fs, fm, fe, fi, fn := funpack64(f) + + switch { + case fi, fn: // NaN + return 0, false + + case fe < -1: // f < 0.5 + return 0, false + + case fe > 63: // f >= 2^63 + if fs != 0 && fm == 0 { // f == -2^63 + return -1 << 63, true + } + if fs != 0 { + return 0, false + } + return 0, false + } + + for fe > int(mantbits64) { + fe-- + fm <<= 1 + } + for fe < int(mantbits64) { + fe++ + fm >>= 1 + } + val = int64(fm) + if fs != 0 { + val = -val + } + return val, true +} + +func fintto64(val int64) (f uint64) { + fs := uint64(val) & (1 << 63) + mant := uint64(val) + if fs != 0 { + mant = -mant + } + return fpack64(fs, mant, int(mantbits64), 0) +} +func fintto32(val int64) (f uint32) { + fs := uint64(val) & (1 << 63) + mant := uint64(val) + if fs != 0 { + mant = -mant + } + // Reduce mantissa size until it fits into a uint32. + // Keep track of the bits we throw away, and if any are + // nonzero or them into the lowest bit. + exp := int(mantbits32) + var trunc uint32 + for mant >= 1<<32 { + trunc |= uint32(mant) & 1 + mant >>= 1 + exp++ + } + + return fpack32(uint32(fs>>32), uint32(mant), exp, trunc) +} + +// 64x64 -> 128 multiply. +// adapted from hacker's delight. +func mullu(u, v uint64) (lo, hi uint64) { + const ( + s = 32 + mask = 1<> s + v0 := v & mask + v1 := v >> s + w0 := u0 * v0 + t := u1*v0 + w0>>s + w1 := t & mask + w2 := t >> s + w1 += u0 * v1 + return u * v, u1*v1 + w2 + w1>>s +} + +// 128/64 -> 64 quotient, 64 remainder. +// adapted from hacker's delight +func divlu(u1, u0, v uint64) (q, r uint64) { + const b = 1 << 32 + + if u1 >= v { + return 1<<64 - 1, 1<<64 - 1 + } + + // s = nlz(v); v <<= s + s := uint(0) + for v&(1<<63) == 0 { + s++ + v <<= 1 + } + + vn1 := v >> 32 + vn0 := v & (1<<32 - 1) + un32 := u1<>(64-s) + un10 := u0 << s + un1 := un10 >> 32 + un0 := un10 & (1<<32 - 1) + q1 := un32 / vn1 + rhat := un32 - q1*vn1 + +again1: + if q1 >= b || q1*vn0 > b*rhat+un1 { + q1-- + rhat += vn1 + if rhat < b { + goto again1 + } + } + + un21 := un32*b + un1 - q1*v + q0 := un21 / vn1 + rhat = un21 - q0*vn1 + +again2: + if q0 >= b || q0*vn0 > b*rhat+un0 { + q0-- + rhat += vn1 + if rhat < b { + goto again2 + } + } + + return q1*b + q0, (un21*b + un0 - q0*v) >> s +} + +func fadd32(x, y uint32) uint32 { + return f64to32(fadd64(f32to64(x), f32to64(y))) +} + +func fmul32(x, y uint32) uint32 { + return f64to32(fmul64(f32to64(x), f32to64(y))) +} + +func fdiv32(x, y uint32) uint32 { + // TODO: are there double-rounding problems here? See issue 48807. + return f64to32(fdiv64(f32to64(x), f32to64(y))) +} + +func feq32(x, y uint32) bool { + cmp, nan := fcmp64(f32to64(x), f32to64(y)) + return cmp == 0 && !nan +} + +func fgt32(x, y uint32) bool { + cmp, nan := fcmp64(f32to64(x), f32to64(y)) + return cmp >= 1 && !nan +} + +func fge32(x, y uint32) bool { + cmp, nan := fcmp64(f32to64(x), f32to64(y)) + return cmp >= 0 && !nan +} + +func feq64(x, y uint64) bool { + cmp, nan := fcmp64(x, y) + return cmp == 0 && !nan +} + +func fgt64(x, y uint64) bool { + cmp, nan := fcmp64(x, y) + return cmp >= 1 && !nan +} + +func fge64(x, y uint64) bool { + cmp, nan := fcmp64(x, y) + return cmp >= 0 && !nan +} + +func fint32to32(x int32) uint32 { + return fintto32(int64(x)) +} + +func fint32to64(x int32) uint64 { + return fintto64(int64(x)) +} + +func fint64to32(x int64) uint32 { + return fintto32(x) +} + +func fint64to64(x int64) uint64 { + return fintto64(x) +} + +func f32toint32(x uint32) int32 { + val, _ := f64toint(f32to64(x)) + return int32(val) +} + +func f32toint64(x uint32) int64 { + val, _ := f64toint(f32to64(x)) + return val +} + +func f64toint32(x uint64) int32 { + val, _ := f64toint(x) + return int32(val) +} + +func f64toint64(x uint64) int64 { + val, _ := f64toint(x) + return val +} + +func f64touint64(x uint64) uint64 { + var m uint64 = 0x43e0000000000000 // float64 1<<63 + if fgt64(m, x) { + return uint64(f64toint64(x)) + } + y := fadd64(x, -m) + z := uint64(f64toint64(y)) + return z | (1 << 63) +} + +func f32touint64(x uint32) uint64 { + var m uint32 = 0x5f000000 // float32 1<<63 + if fgt32(m, x) { + return uint64(f32toint64(x)) + } + y := fadd32(x, -m) + z := uint64(f32toint64(y)) + return z | (1 << 63) +} + +func fuint64to64(x uint64) uint64 { + if int64(x) >= 0 { + return fint64to64(int64(x)) + } + // See ../cmd/compile/internal/ssagen/ssa.go:uint64Tofloat + y := x & 1 + z := x >> 1 + z = z | y + r := fint64to64(int64(z)) + return fadd64(r, r) +} + +func fuint64to32(x uint64) uint32 { + if int64(x) >= 0 { + return fint64to32(int64(x)) + } + // See ../cmd/compile/internal/ssagen/ssa.go:uint64Tofloat + y := x & 1 + z := x >> 1 + z = z | y + r := fint64to32(int64(z)) + return fadd32(r, r) +} diff --git a/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64_test.go b/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64_test.go new file mode 100644 index 00000000000..c57fe08b0ef --- /dev/null +++ b/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64_test.go @@ -0,0 +1,204 @@ +// Code generated by copy.sh. DO NOT EDIT. +// This file is copied from $GOROOT/src/runtime/softfloat64_test.go. +// It is the tests for the software floating point implementation +// used by the Go runtime. + +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package softfloat_test + +import ( + "math" + "math/rand" + . "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat" + "testing" + "runtime" +) + +// turn uint64 op into float64 op +func fop(f func(x, y uint64) uint64) func(x, y float64) float64 { + return func(x, y float64) float64 { + bx := math.Float64bits(x) + by := math.Float64bits(y) + return math.Float64frombits(f(bx, by)) + } +} + +func add(x, y float64) float64 { return x + y } +func sub(x, y float64) float64 { return x - y } +func mul(x, y float64) float64 { return x * y } +func div(x, y float64) float64 { return x / y } + +func TestFloat64(t *testing.T) { + base := []float64{ + 0, + math.Copysign(0, -1), + -1, + 1, + math.NaN(), + math.Inf(+1), + math.Inf(-1), + 0.1, + 1.5, + 1.9999999999999998, // all 1s mantissa + 1.3333333333333333, // 1.010101010101... + 1.1428571428571428, // 1.001001001001... + 1.112536929253601e-308, // first normal + 2, + 4, + 8, + 16, + 32, + 64, + 128, + 256, + 3, + 12, + 1234, + 123456, + -0.1, + -1.5, + -1.9999999999999998, + -1.3333333333333333, + -1.1428571428571428, + -2, + -3, + 1e-200, + 1e-300, + 1e-310, + 5e-324, + 1e-105, + 1e-305, + 1e+200, + 1e+306, + 1e+307, + 1e+308, + } + all := make([]float64, 200) + copy(all, base) + for i := len(base); i < len(all); i++ { + all[i] = rand.NormFloat64() + } + + test(t, "+", add, fop(Fadd64), all) + test(t, "-", sub, fop(Fsub64), all) + if runtime.GOARCH != "386" { // 386 is not precise! + test(t, "*", mul, fop(Fmul64), all) + test(t, "/", div, fop(Fdiv64), all) + } +} + +// 64 -hw-> 32 -hw-> 64 +func trunc32(f float64) float64 { + return float64(float32(f)) +} + +// 64 -sw->32 -hw-> 64 +func to32sw(f float64) float64 { + return float64(math.Float32frombits(F64to32(math.Float64bits(f)))) +} + +// 64 -hw->32 -sw-> 64 +func to64sw(f float64) float64 { + return math.Float64frombits(F32to64(math.Float32bits(float32(f)))) +} + +// float64 -hw-> int64 -hw-> float64 +func hwint64(f float64) float64 { + return float64(int64(f)) +} + +// float64 -hw-> int32 -hw-> float64 +func hwint32(f float64) float64 { + return float64(int32(f)) +} + +// float64 -sw-> int64 -hw-> float64 +func toint64sw(f float64) float64 { + i, ok := F64toint(math.Float64bits(f)) + if !ok { + // There's no right answer for out of range. + // Match the hardware to pass the test. + i = int64(f) + } + return float64(i) +} + +// float64 -hw-> int64 -sw-> float64 +func fromint64sw(f float64) float64 { + return math.Float64frombits(Fintto64(int64(f))) +} + +var nerr int + +func err(t *testing.T, format string, args ...any) { + t.Errorf(format, args...) + + // cut errors off after a while. + // otherwise we spend all our time + // allocating memory to hold the + // formatted output. + if nerr++; nerr >= 10 { + t.Fatal("too many errors") + } +} + +func test(t *testing.T, op string, hw, sw func(float64, float64) float64, all []float64) { + for _, f := range all { + for _, g := range all { + h := hw(f, g) + s := sw(f, g) + if !same(h, s) { + err(t, "%g %s %g = sw %g, hw %g\n", f, op, g, s, h) + } + testu(t, "to32", trunc32, to32sw, h) + testu(t, "to64", trunc32, to64sw, h) + testu(t, "toint64", hwint64, toint64sw, h) + testu(t, "fromint64", hwint64, fromint64sw, h) + testcmp(t, f, h) + testcmp(t, h, f) + testcmp(t, g, h) + testcmp(t, h, g) + } + } +} + +func testu(t *testing.T, op string, hw, sw func(float64) float64, v float64) { + h := hw(v) + s := sw(v) + if !same(h, s) { + err(t, "%s %g = sw %g, hw %g\n", op, v, s, h) + } +} + +func hwcmp(f, g float64) (cmp int, isnan bool) { + switch { + case f < g: + return -1, false + case f > g: + return +1, false + case f == g: + return 0, false + } + return 0, true // must be NaN +} + +func testcmp(t *testing.T, f, g float64) { + hcmp, hisnan := hwcmp(f, g) + scmp, sisnan := Fcmp64(math.Float64bits(f), math.Float64bits(g)) + if int32(hcmp) != scmp || hisnan != sisnan { + err(t, "cmp(%g, %g) = sw %v, %v, hw %v, %v\n", f, g, scmp, sisnan, hcmp, hisnan) + } +} + +func same(f, g float64) bool { + if math.IsNaN(f) && math.IsNaN(g) { + return true + } + if math.Copysign(1, f) != math.Copysign(1, g) { + return false + } + return f == g +} diff --git a/gnovm/pkg/gnolang/internal/softfloat/softfloat.go b/gnovm/pkg/gnolang/internal/softfloat/softfloat.go new file mode 100644 index 00000000000..30f66dff620 --- /dev/null +++ b/gnovm/pkg/gnolang/internal/softfloat/softfloat.go @@ -0,0 +1,134 @@ +// Package softfloat is a copy of the Go runtime's softfloat64.go file. +// It is a pure software floating point implementation. It can be used to +// perform determinstic, hardware-independent floating point computations. +// +// This package uses shortnames to refer to its different operations. Here is a +// quick reference: +// +// add f + g +// sub f - g +// mul f * g +// div f / g +// neg (- f) +// eq f == g +// gt f > g +// ge f >= g +package softfloat + +// This file mostly exports the functions from runtime_softfloat64.go + +//go:generate sh copy.sh + +const ( + mask = 0x7FF + shift = 64 - 11 - 1 + bias = 1023 +) + +func Fadd64(f, g uint64) uint64 { return fadd64(f, g) } +func Fsub64(f, g uint64) uint64 { return fsub64(f, g) } +func Fmul64(f, g uint64) uint64 { return fmul64(f, g) } +func Fdiv64(f, g uint64) uint64 { return fdiv64(f, g) } +func Fneg64(f uint64) uint64 { return fneg64(f) } +func Feq64(f, g uint64) bool { return feq64(f, g) } +func Fgt64(f, g uint64) bool { return fgt64(f, g) } +func Fge64(f, g uint64) bool { return fge64(f, g) } + +func Fadd32(f, g uint32) uint32 { return fadd32(f, g) } +func Fsub32(f, g uint32) uint32 { return fadd32(f, Fneg32(g)) } +func Fmul32(f, g uint32) uint32 { return fmul32(f, g) } +func Fdiv32(f, g uint32) uint32 { return fdiv32(f, g) } +func Feq32(f, g uint32) bool { return feq32(f, g) } +func Fgt32(f, g uint32) bool { return fgt32(f, g) } +func Fge32(f, g uint32) bool { return fge32(f, g) } +func Flt32(f, g uint32) bool { + cmp, nan := fcmp64(f32to64(f), f32to64(g)) + return cmp <= -1 && !nan +} + +func Fle32(f, g uint32) bool { + cmp, nan := fcmp64(f32to64(f), f32to64(g)) + return cmp <= 0 && !nan +} + +func Flt64(f, g uint64) bool { + cmp, nan := fcmp64(f, g) + return cmp <= -1 && !nan +} + +func Fle64(f, g uint64) bool { + cmp, nan := fcmp64(f, g) + return cmp <= 0 && !nan +} + +func Fcmp64(f, g uint64) (cmp int32, isnan bool) { return fcmp64(f, g) } + +func Fneg32(f uint32) uint32 { + // Not defined in runtime - this is a copy similar to fneg64. + return f ^ (1 << (mantbits32 + expbits32)) +} + +// Conversions + +func Fintto64(val int64) (f uint64) { return fintto64(val) } +func Fintto32(val int64) (f uint32) { return fintto32(val) } + +func F32to64(f uint32) uint64 { return f32to64(f) } +func F32toint32(x uint32) int32 { return f32toint32(x) } +func F32toint64(x uint32) int64 { return f32toint64(x) } +func F32touint64(x uint32) uint64 { return f32touint64(x) } +func F64to32(f uint64) uint32 { return f64to32(f) } +func F64toint(f uint64) (val int64, ok bool) { return f64toint(f) } +func F64toint32(x uint64) int32 { return f64toint32(x) } +func F64toint64(x uint64) int64 { return f64toint64(x) } +func F64touint64(x uint64) uint64 { return f64touint64(x) } +func Fint32to32(x int32) uint32 { return fint32to32(x) } +func Fint32to64(x int32) uint64 { return fint32to64(x) } +func Fint64to32(x int64) uint32 { return fint64to32(x) } +func Fint64to64(x int64) uint64 { return fint64to64(x) } +func Fuint64to32(x uint64) uint32 { return fuint64to32(x) } +func Fuint64to64(x uint64) uint64 { return fuint64to64(x) } + +// unpack64 unpacks the float64 f into sign, exp, mantissa, isInf, isNaN. + +func Funpack32(f uint32) (sign, mant uint32, exp int, inf, nan bool) { return funpack32(f) } +func Funpack64(f uint64) (sign, mant uint64, exp int, inf, nan bool) { return funpack64(f) } + +// Trunc + +func Ftrunc64(f uint64) uint64 { return trunc(f) } +func Ftrunc32(f uint32) uint32 { return f64to32(trunc(f32to64(f))) } + +func trunc(x uint64) uint64 { + cmp, _ := Fcmp64(x, Fintto64(0)) + if _, _, _, isInf, IsNaN := Funpack64(x); cmp == 0 || isInf || IsNaN { + return x + } + + d, _ := modf(x) + return d +} + +func modf(u uint64) (it uint64, frac uint64) { + if Flt64(u, fint64to64(1)) { + switch { + case Flt64(u, fint64to64(0)): + it, frac = modf(Fneg64(u)) + return -it, -frac + case feq64(u, fint64to64(0)): + return u, u // Return -0, -0 when f == -0 + } + return 0, u + } + + it = u + e := uint(it>>shift)&mask - bias + + // Keep the top 12+e bits, the integer part; clear the rest. + if e < 64-12 { + it &^= 1<<(64-12-e) - 1 + } + + frac = fsub64(u, it) + return +} diff --git a/gnovm/pkg/gnolang/op_binary.go b/gnovm/pkg/gnolang/op_binary.go index 0f66da5e685..765f3ccbfbd 100644 --- a/gnovm/pkg/gnolang/op_binary.go +++ b/gnovm/pkg/gnolang/op_binary.go @@ -6,6 +6,7 @@ import ( "math/big" "github.com/cockroachdb/apd/v3" + "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat" "github.com/gnolang/gno/tm2/pkg/overflow" ) @@ -394,9 +395,9 @@ func isEql(store Store, lv, rv *TypedValue) bool { case Uint64Kind: return (lv.GetUint64() == rv.GetUint64()) case Float32Kind: - return (lv.GetFloat32() == rv.GetFloat32()) // XXX determinism? + return softfloat.Feq32(lv.GetFloat32(), rv.GetFloat32()) case Float64Kind: - return (lv.GetFloat64() == rv.GetFloat64()) // XXX determinism? + return softfloat.Feq64(lv.GetFloat64(), rv.GetFloat64()) case BigintKind: lb := lv.V.(BigintValue).V rb := rv.V.(BigintValue).V @@ -535,9 +536,9 @@ func isLss(lv, rv *TypedValue) bool { case Uint64Kind: return (lv.GetUint64() < rv.GetUint64()) case Float32Kind: - return (lv.GetFloat32() < rv.GetFloat32()) // XXX determinism? + return softfloat.Flt32(lv.GetFloat32(), rv.GetFloat32()) case Float64Kind: - return (lv.GetFloat64() < rv.GetFloat64()) // XXX determinism? + return softfloat.Flt64(lv.GetFloat64(), rv.GetFloat64()) case BigintKind: lb := lv.V.(BigintValue).V rb := rv.V.(BigintValue).V @@ -579,9 +580,9 @@ func isLeq(lv, rv *TypedValue) bool { case Uint64Kind: return (lv.GetUint64() <= rv.GetUint64()) case Float32Kind: - return (lv.GetFloat32() <= rv.GetFloat32()) // XXX determinism? + return softfloat.Fle32(lv.GetFloat32(), rv.GetFloat32()) case Float64Kind: - return (lv.GetFloat64() <= rv.GetFloat64()) // XXX determinism? + return softfloat.Fle64(lv.GetFloat64(), rv.GetFloat64()) case BigintKind: lb := lv.V.(BigintValue).V rb := rv.V.(BigintValue).V @@ -623,9 +624,9 @@ func isGtr(lv, rv *TypedValue) bool { case Uint64Kind: return (lv.GetUint64() > rv.GetUint64()) case Float32Kind: - return (lv.GetFloat32() > rv.GetFloat32()) // XXX determinism? + return softfloat.Fgt32(lv.GetFloat32(), rv.GetFloat32()) case Float64Kind: - return (lv.GetFloat64() > rv.GetFloat64()) // XXX determinism? + return softfloat.Fgt64(lv.GetFloat64(), rv.GetFloat64()) case BigintKind: lb := lv.V.(BigintValue).V rb := rv.V.(BigintValue).V @@ -667,9 +668,9 @@ func isGeq(lv, rv *TypedValue) bool { case Uint64Kind: return (lv.GetUint64() >= rv.GetUint64()) case Float32Kind: - return (lv.GetFloat32() >= rv.GetFloat32()) // XXX determinism? + return softfloat.Fge32(lv.GetFloat32(), rv.GetFloat32()) case Float64Kind: - return (lv.GetFloat64() >= rv.GetFloat64()) // XXX determinism? + return softfloat.Fge64(lv.GetFloat64(), rv.GetFloat64()) case BigintKind: lb := lv.V.(BigintValue).V rb := rv.V.(BigintValue).V @@ -732,10 +733,10 @@ func addAssign(alloc *Allocator, lv, rv *TypedValue) *Exception { lv.SetUint64(lv.GetUint64() + rv.GetUint64()) case Float32Type: // NOTE: gno doesn't fuse *+. - lv.SetFloat32(lv.GetFloat32() + rv.GetFloat32()) // XXX determinism? + lv.SetFloat32(softfloat.Fadd32(lv.GetFloat32(), rv.GetFloat32())) case Float64Type: // NOTE: gno doesn't fuse *+. - lv.SetFloat64(lv.GetFloat64() + rv.GetFloat64()) // XXX determinism? + lv.SetFloat64(softfloat.Fadd64(lv.GetFloat64(), rv.GetFloat64())) case BigintType, UntypedBigintType: lb := lv.GetBigInt() lb = big.NewInt(0).Add(lb, rv.GetBigInt()) @@ -807,10 +808,10 @@ func subAssign(lv, rv *TypedValue) *Exception { lv.SetUint64(lv.GetUint64() - rv.GetUint64()) case Float32Type: // NOTE: gno doesn't fuse *+. - lv.SetFloat32(lv.GetFloat32() - rv.GetFloat32()) // XXX determinism? + lv.SetFloat32(softfloat.Fsub32(lv.GetFloat32(), rv.GetFloat32())) case Float64Type: // NOTE: gno doesn't fuse *+. - lv.SetFloat64(lv.GetFloat64() - rv.GetFloat64()) // XXX determinism? + lv.SetFloat64(softfloat.Fsub64(lv.GetFloat64(), rv.GetFloat64())) case BigintType, UntypedBigintType: lb := lv.GetBigInt() lb = big.NewInt(0).Sub(lb, rv.GetBigInt()) @@ -879,10 +880,10 @@ func mulAssign(lv, rv *TypedValue) *Exception { lv.SetUint64(lv.GetUint64() * rv.GetUint64()) case Float32Type: // NOTE: gno doesn't fuse *+. - lv.SetFloat32(lv.GetFloat32() * rv.GetFloat32()) // XXX determinism? + lv.SetFloat32(softfloat.Fmul32(lv.GetFloat32(), rv.GetFloat32())) case Float64Type: // NOTE: gno doesn't fuse *+. - lv.SetFloat64(lv.GetFloat64() * rv.GetFloat64()) // XXX determinism? + lv.SetFloat64(softfloat.Fmul64(lv.GetFloat64(), rv.GetFloat64())) case BigintType, UntypedBigintType: lb := lv.GetBigInt() lb = big.NewInt(0).Mul(lb, rv.GetBigInt()) @@ -975,20 +976,17 @@ func quoAssign(lv, rv *TypedValue) *Exception { // XXX Handling float overflows is more complex. case Float32Type: // NOTE: gno doesn't fuse *+. - y := rv.GetFloat32() - ok = y != 0 + ok = !softfloat.Feq32(rv.GetFloat32(), softfloat.Fintto32(0)) + if ok { - lv.SetFloat32(lv.GetFloat32() / y) + lv.SetFloat32(softfloat.Fdiv32(lv.GetFloat32(), rv.GetFloat32())) } - // XXX FOR DETERMINISM, PANIC IF NAN. case Float64Type: // NOTE: gno doesn't fuse *+. - y := rv.GetFloat64() - ok = y != 0 + ok = !softfloat.Feq64(rv.GetFloat64(), softfloat.Fintto64(0)) if ok { - lv.SetFloat64(lv.GetFloat64() / y) + lv.SetFloat64(softfloat.Fdiv64(lv.GetFloat64(), rv.GetFloat64())) } - // XXX FOR DETERMINISM, PANIC IF NAN. case BigintType, UntypedBigintType: if rv.GetBigInt().Sign() == 0 { ok = false diff --git a/gnovm/pkg/gnolang/op_inc_dec.go b/gnovm/pkg/gnolang/op_inc_dec.go index 1e68e195596..c67a4be6ed5 100644 --- a/gnovm/pkg/gnolang/op_inc_dec.go +++ b/gnovm/pkg/gnolang/op_inc_dec.go @@ -5,6 +5,7 @@ import ( "math/big" "github.com/cockroachdb/apd/v3" + "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat" "github.com/gnolang/gno/tm2/pkg/overflow" ) @@ -57,9 +58,9 @@ func (m *Machine) doOpInc() { case Uint64Type: lv.SetUint64(lv.GetUint64() + 1) case Float32Type: - lv.SetFloat32(lv.GetFloat32() + 1) + lv.SetFloat32(softfloat.Fadd32(lv.GetFloat32(), softfloat.Fintto32(1))) case Float64Type: - lv.SetFloat64(lv.GetFloat64() + 1) + lv.SetFloat64(softfloat.Fadd64(lv.GetFloat64(), softfloat.Fintto64(1))) case BigintType, UntypedBigintType: lb := lv.GetBigInt() lb = big.NewInt(0).Add(lb, big.NewInt(1)) @@ -129,9 +130,9 @@ func (m *Machine) doOpDec() { case Uint64Type: lv.SetUint64(lv.GetUint64() - 1) case Float32Type: - lv.SetFloat32(lv.GetFloat32() - 1) + lv.SetFloat32(softfloat.Fsub32(lv.GetFloat32(), softfloat.Fintto32(1))) case Float64Type: - lv.SetFloat64(lv.GetFloat64() - 1) + lv.SetFloat64(softfloat.Fsub64(lv.GetFloat64(), softfloat.Fintto64(1))) case BigintType, UntypedBigintType: lb := lv.GetBigInt() lb = big.NewInt(0).Sub(lb, big.NewInt(1)) diff --git a/gnovm/pkg/gnolang/op_unary.go b/gnovm/pkg/gnolang/op_unary.go index 9c330c7f8f1..469c80b8dac 100644 --- a/gnovm/pkg/gnolang/op_unary.go +++ b/gnovm/pkg/gnolang/op_unary.go @@ -5,6 +5,7 @@ import ( "math/big" "github.com/cockroachdb/apd/v3" + "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat" ) func (m *Machine) doOpUpos() { @@ -46,9 +47,9 @@ func (m *Machine) doOpUneg() { case Uint64Type: xv.SetUint64(-xv.GetUint64()) case Float32Type: - xv.SetFloat32(-xv.GetFloat32()) + xv.SetFloat32(softfloat.Fneg32(xv.GetFloat32())) case Float64Type: - xv.SetFloat64(-xv.GetFloat64()) + xv.SetFloat64(softfloat.Fneg64(xv.GetFloat64())) case UntypedBigintType, BigintType: bv := xv.V.(BigintValue) xv.V = BigintValue{V: new(big.Int).Neg(bv.V)} diff --git a/gnovm/pkg/gnolang/values.go b/gnovm/pkg/gnolang/values.go index 4c2e2835f95..da887764c8e 100644 --- a/gnovm/pkg/gnolang/values.go +++ b/gnovm/pkg/gnolang/values.go @@ -3,7 +3,6 @@ package gnolang import ( "encoding/binary" "fmt" - "math" "math/big" "reflect" "strconv" @@ -1121,13 +1120,13 @@ func (tv *TypedValue) PrimitiveBytes() (data []byte) { return data case Float32Type: data = make([]byte, 4) - u32 := math.Float32bits(tv.GetFloat32()) + u32 := tv.GetFloat32() binary.LittleEndian.PutUint32( data, u32) return data case Float64Type: data = make([]byte, 8) - u64 := math.Float64bits(tv.GetFloat64()) + u64 := tv.GetFloat64() binary.LittleEndian.PutUint64( data, u64) return data @@ -1450,7 +1449,7 @@ func (tv *TypedValue) GetUint64() uint64 { return *(*uint64)(unsafe.Pointer(&tv.N)) } -func (tv *TypedValue) SetFloat32(n float32) { +func (tv *TypedValue) SetFloat32(n uint32) { if debug { if tv.T.Kind() != Float32Kind || isNative(tv.T) { panic(fmt.Sprintf( @@ -1458,10 +1457,10 @@ func (tv *TypedValue) SetFloat32(n float32) { tv.T.String())) } } - *(*float32)(unsafe.Pointer(&tv.N)) = n + *(*uint32)(unsafe.Pointer(&tv.N)) = n } -func (tv *TypedValue) GetFloat32() float32 { +func (tv *TypedValue) GetFloat32() uint32 { if debug { if tv.T != nil && tv.T.Kind() != Float32Kind { panic(fmt.Sprintf( @@ -1469,10 +1468,10 @@ func (tv *TypedValue) GetFloat32() float32 { tv.T.String())) } } - return *(*float32)(unsafe.Pointer(&tv.N)) + return *(*uint32)(unsafe.Pointer(&tv.N)) } -func (tv *TypedValue) SetFloat64(n float64) { +func (tv *TypedValue) SetFloat64(n uint64) { if debug { if tv.T.Kind() != Float64Kind || isNative(tv.T) { panic(fmt.Sprintf( @@ -1480,10 +1479,10 @@ func (tv *TypedValue) SetFloat64(n float64) { tv.T.String())) } } - *(*float64)(unsafe.Pointer(&tv.N)) = n + *(*uint64)(unsafe.Pointer(&tv.N)) = n } -func (tv *TypedValue) GetFloat64() float64 { +func (tv *TypedValue) GetFloat64() uint64 { if debug { if tv.T != nil && tv.T.Kind() != Float64Kind { panic(fmt.Sprintf( @@ -1491,7 +1490,7 @@ func (tv *TypedValue) GetFloat64() float64 { tv.T.String())) } } - return *(*float64)(unsafe.Pointer(&tv.N)) + return *(*uint64)(unsafe.Pointer(&tv.N)) } func (tv *TypedValue) GetBigInt() *big.Int { diff --git a/gnovm/pkg/gnolang/values_conversions.go b/gnovm/pkg/gnolang/values_conversions.go index baeded76c1a..e1c9378fe67 100644 --- a/gnovm/pkg/gnolang/values_conversions.go +++ b/gnovm/pkg/gnolang/values_conversions.go @@ -8,6 +8,7 @@ import ( "strconv" "github.com/cockroachdb/apd/v3" + "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat" ) // t cannot be nil or untyped or DataByteType. @@ -163,11 +164,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetInt()) // XXX determinism? + x := softfloat.Fintto32(int64(tv.GetInt())) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetInt()) // XXX determinism? + x := softfloat.Fintto64(int64(tv.GetInt())) tv.T = t tv.SetFloat64(x) case StringKind: @@ -233,11 +234,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetInt8()) // XXX determinism? + x := softfloat.Fint32to32(int32(tv.GetInt8())) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetInt8()) // XXX determinism? + x := softfloat.Fint32to64(int32(tv.GetInt8())) tv.T = t tv.SetFloat64(x) case StringKind: @@ -304,11 +305,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetInt16()) // XXX determinism? + x := softfloat.Fint32to32(int32(tv.GetInt16())) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetInt16()) // XXX determinism? + x := softfloat.Fint32to64(int32(tv.GetInt16())) tv.T = t tv.SetFloat64(x) case StringKind: @@ -379,11 +380,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetInt32()) // XXX determinism? + x := softfloat.Fint32to32(tv.GetInt32()) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetInt32()) // XXX determinism? + x := softfloat.Fint32to64(tv.GetInt32()) tv.T = t tv.SetFloat64(x) case StringKind: @@ -456,11 +457,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetInt64()) // XXX determinism? + x := softfloat.Fint64to32(tv.GetInt64()) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetInt64()) // XXX determinism? + x := softfloat.Fint64to64(tv.GetInt64()) tv.T = t tv.SetFloat64(x) case StringKind: @@ -533,11 +534,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetUint()) // XXX determinism? + x := softfloat.Fuint64to32(uint64(tv.GetUint())) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetUint()) // XXX determinism? + x := softfloat.Fuint64to64(uint64(tv.GetUint())) tv.T = t tv.SetFloat64(x) case StringKind: @@ -602,11 +603,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetUint8()) // XXX determinism? + x := softfloat.Fuint64to32(uint64(tv.GetUint8())) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetUint8()) // XXX determinism? + x := softfloat.Fuint64to64(uint64(tv.GetUint8())) tv.T = t tv.SetFloat64(x) case StringKind: @@ -673,11 +674,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetUint16()) // XXX determinism? + x := softfloat.Fuint64to32(uint64(tv.GetUint16())) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetUint16()) // XXX determinism? + x := softfloat.Fuint64to64(uint64(tv.GetUint16())) tv.T = t tv.SetFloat64(x) case StringKind: @@ -746,11 +747,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetUint32()) // XXX determinism? + x := softfloat.Fuint64to32(uint64(tv.GetUint32())) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetUint32()) // XXX determinism? + x := softfloat.Fuint64to64(uint64(tv.GetUint32())) tv.T = t tv.SetFloat64(x) case StringKind: @@ -825,11 +826,11 @@ GNO_CASE: tv.T = t tv.SetUint64(x) case Float32Kind: - x := float32(tv.GetUint64()) // XXX determinism? + x := softfloat.Fuint64to32(tv.GetUint64()) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetUint64()) // XXX determinism? + x := softfloat.Fuint64to64(tv.GetUint64()) tv.T = t tv.SetFloat64(x) case StringKind: @@ -847,156 +848,155 @@ GNO_CASE: switch k { case IntKind: validate(Float32Kind, IntKind, func() bool { - val := float64(tv.GetFloat32()) - trunc := math.Trunc(val) + trunc := softfloat.Ftrunc32(tv.GetFloat32()) - if val != trunc { + if !softfloat.Feq32(trunc, tv.GetFloat32()) { return false } - return int64(trunc) >= math.MinInt && int64(trunc) <= math.MaxInt + truncInt64 := softfloat.F32toint64(trunc) + return truncInt64 >= math.MinInt && truncInt64 <= math.MaxInt }) - x := int(tv.GetFloat32()) // XXX determinism? + x := int(softfloat.F32toint64(tv.GetFloat32())) tv.T = t tv.SetInt(x) case Int8Kind: validate(Float32Kind, Int8Kind, func() bool { - val := float64(tv.GetFloat32()) - trunc := math.Trunc(val) + trunc := softfloat.Ftrunc32(tv.GetFloat32()) - if val != trunc { + if !softfloat.Feq32(trunc, tv.GetFloat32()) { return false } - return int64(trunc) >= math.MinInt8 && int64(trunc) <= math.MaxInt8 + truncInt64 := softfloat.F32toint64(trunc) + return truncInt64 >= math.MinInt8 && truncInt64 <= math.MaxInt8 }) - x := int8(tv.GetFloat32()) // XXX determinism? + x := int8(softfloat.F32toint32(tv.GetFloat32())) tv.T = t tv.SetInt8(x) case Int16Kind: validate(Float32Kind, Int16Kind, func() bool { - val := float64(tv.GetFloat32()) - trunc := math.Trunc(val) + trunc := softfloat.Ftrunc32(tv.GetFloat32()) - if val != trunc { + if !softfloat.Feq32(trunc, tv.GetFloat32()) { return false } - return int64(trunc) >= math.MinInt16 && int64(trunc) <= math.MaxInt16 + truncInt64 := softfloat.F32toint64(trunc) + return truncInt64 >= math.MinInt16 && truncInt64 <= math.MaxInt16 }) - x := int16(tv.GetFloat32()) // XXX determinism? + x := int16(softfloat.F32toint32(tv.GetFloat32())) tv.T = t tv.SetInt16(x) case Int32Kind: validate(Float32Kind, Int32Kind, func() bool { - val := float64(tv.GetFloat32()) - trunc := math.Trunc(val) + trunc := softfloat.Ftrunc32(tv.GetFloat32()) - if val != trunc { + if !softfloat.Feq32(trunc, tv.GetFloat32()) { return false } - return int64(trunc) >= math.MinInt32 && int64(trunc) <= math.MaxInt32 + truncInt64 := softfloat.F32toint64(trunc) + return truncInt64 >= math.MinInt32 && truncInt64 <= math.MaxInt32 }) - x := int32(tv.GetFloat32()) // XXX determinism? + x := softfloat.F32toint32(tv.GetFloat32()) tv.T = t tv.SetInt32(x) case Int64Kind: validate(Float32Kind, Int64Kind, func() bool { - val := float64(tv.GetFloat32()) - trunc := math.Trunc(val) + trunc := softfloat.Ftrunc32(tv.GetFloat32()) - return val == trunc + return softfloat.Feq32(trunc, tv.GetFloat32()) }) - x := int64(tv.GetFloat32()) // XXX determinism? + x := softfloat.F32toint64(tv.GetFloat32()) tv.T = t tv.SetInt64(x) case UintKind: validate(Float32Kind, UintKind, func() bool { - val := float64(tv.GetFloat32()) - trunc := math.Trunc(val) + trunc := softfloat.Ftrunc32(tv.GetFloat32()) - if val != trunc { + if !softfloat.Feq32(trunc, tv.GetFloat32()) { return false } - return trunc >= 0 && trunc <= math.MaxUint + truncUint64 := softfloat.F32touint64(trunc) + return truncUint64 >= 0 && truncUint64 <= math.MaxUint }) - x := uint(tv.GetFloat32()) // XXX determinism? + x := uint(softfloat.F32touint64(tv.GetFloat32())) tv.T = t tv.SetUint(x) case Uint8Kind: validate(Float32Kind, Uint8Kind, func() bool { - val := float64(tv.GetFloat32()) - trunc := math.Trunc(val) + trunc := softfloat.Ftrunc32(tv.GetFloat32()) - if val != trunc { + if !softfloat.Feq32(trunc, tv.GetFloat32()) { return false } - return int64(trunc) >= 0 && int64(trunc) <= math.MaxUint8 + truncUint64 := softfloat.F32touint64(trunc) + return truncUint64 >= 0 && truncUint64 <= math.MaxUint8 }) - x := uint8(tv.GetFloat32()) // XXX determinism? + x := uint8(softfloat.F32touint64(tv.GetFloat32())) tv.T = t tv.SetUint8(x) case Uint16Kind: validate(Float32Kind, Uint16Kind, func() bool { - val := float64(tv.GetFloat32()) - trunc := math.Trunc(val) + trunc := softfloat.Ftrunc32(tv.GetFloat32()) - if val != trunc { + if !softfloat.Feq32(trunc, tv.GetFloat32()) { return false } - return int64(trunc) >= 0 && int64(trunc) <= math.MaxUint16 + truncUint64 := softfloat.F32touint64(trunc) + return truncUint64 >= 0 && truncUint64 <= math.MaxUint16 }) - x := uint16(tv.GetFloat32()) // XXX determinism? + x := uint16(softfloat.F32touint64(tv.GetFloat32())) tv.T = t tv.SetUint16(x) case Uint32Kind: validate(Float32Kind, Uint32Kind, func() bool { - val := float64(tv.GetFloat32()) - trunc := math.Trunc(val) + trunc := softfloat.Ftrunc32(tv.GetFloat32()) - if val != trunc { + if !softfloat.Feq32(trunc, tv.GetFloat32()) { return false } - return int64(trunc) >= 0 && int64(trunc) <= math.MaxUint32 + truncUint64 := softfloat.F32touint64(trunc) + return truncUint64 >= 0 && truncUint64 <= math.MaxUint32 }) - x := uint32(tv.GetFloat32()) // XXX determinism? + x := uint32(softfloat.F32touint64(tv.GetFloat32())) tv.T = t tv.SetUint32(x) case Uint64Kind: validate(Float32Kind, Uint64Kind, func() bool { - val := float64(tv.GetFloat32()) - trunc := math.Trunc(val) + trunc := softfloat.Ftrunc32(tv.GetFloat32()) - if val != trunc { + if !softfloat.Feq32(trunc, tv.GetFloat32()) { return false } - return trunc >= 0 && trunc <= math.MaxUint + truncUint64 := softfloat.F32touint64(trunc) + return truncUint64 >= 0 && truncUint64 <= math.MaxUint }) - x := uint64(tv.GetFloat32()) // XXX determinism? + x := softfloat.F32touint64(tv.GetFloat32()) tv.T = t tv.SetUint64(x) case Float32Kind: - x := tv.GetFloat32() // XXX determinism? + x := tv.GetFloat32() // ??? tv.T = t tv.SetFloat32(x) case Float64Kind: - x := float64(tv.GetFloat32()) // XXX determinism? + x := softfloat.F32to64(tv.GetFloat32()) tv.T = t tv.SetFloat64(x) default: @@ -1008,160 +1008,160 @@ GNO_CASE: switch k { case IntKind: validate(Float64Kind, IntKind, func() bool { - val := tv.GetFloat64() - trunc := math.Trunc(val) + trunc := softfloat.Ftrunc64(tv.GetFloat64()) - if val != trunc { + if !softfloat.Feq64(trunc, tv.GetFloat64()) { return false } - return int64(trunc) >= math.MinInt && int64(trunc) <= math.MaxInt + truncInt64 := softfloat.F64toint64(trunc) + return truncInt64 >= math.MinInt && truncInt64 <= math.MaxInt }) - x := int(tv.GetFloat64()) // XXX determinism? + xp, _ := softfloat.F64toint(tv.GetFloat64()) + x := int(xp) tv.T = t tv.SetInt(x) case Int8Kind: validate(Float64Kind, Int8Kind, func() bool { - val := tv.GetFloat64() - trunc := math.Trunc(val) + trunc := softfloat.Ftrunc64(tv.GetFloat64()) - if val != trunc { + if !softfloat.Feq64(trunc, tv.GetFloat64()) { return false } - return int64(trunc) >= math.MinInt8 && int64(trunc) <= math.MaxInt8 + truncInt64 := softfloat.F64toint64(trunc) + return truncInt64 >= math.MinInt8 && truncInt64 <= math.MaxInt8 }) - x := int8(tv.GetFloat64()) // XXX determinism? + x := int8(softfloat.F64toint32(tv.GetFloat64())) tv.T = t tv.SetInt8(x) case Int16Kind: validate(Float64Kind, Int16Kind, func() bool { - val := tv.GetFloat64() - trunc := math.Trunc(val) + trunc := softfloat.Ftrunc64(tv.GetFloat64()) - if val != trunc { + if !softfloat.Feq64(trunc, tv.GetFloat64()) { return false } - return int64(trunc) >= math.MinInt16 && int64(trunc) <= math.MaxInt16 + truncInt64 := softfloat.F64toint64(trunc) + return truncInt64 >= math.MinInt16 && truncInt64 <= math.MaxInt16 }) - x := int16(tv.GetFloat64()) // XXX determinism? + x := int16(softfloat.F64toint32(tv.GetFloat64())) tv.T = t tv.SetInt16(x) case Int32Kind: validate(Float64Kind, Int32Kind, func() bool { - val := tv.GetFloat64() - trunc := math.Trunc(val) + trunc := softfloat.Ftrunc64(tv.GetFloat64()) - if val != trunc { + if !softfloat.Feq64(trunc, tv.GetFloat64()) { return false } - return int64(trunc) >= math.MinInt32 && int64(trunc) <= math.MaxInt32 + truncInt64 := softfloat.F64toint64(trunc) + return truncInt64 >= math.MinInt32 && truncInt64 <= math.MaxInt32 }) - x := int32(tv.GetFloat64()) // XXX determinism? + x := softfloat.F64toint32(tv.GetFloat64()) tv.T = t tv.SetInt32(x) case Int64Kind: validate(Float64Kind, Int64Kind, func() bool { - val := tv.GetFloat64() - trunc := math.Trunc(val) + trunc := softfloat.Ftrunc64(tv.GetFloat64()) - return val == trunc + return softfloat.Feq64(trunc, tv.GetFloat64()) }) - x := int64(tv.GetFloat64()) // XXX determinism? + x := softfloat.F64toint64(tv.GetFloat64()) tv.T = t tv.SetInt64(x) case UintKind: validate(Float64Kind, UintKind, func() bool { - val := tv.GetFloat64() - trunc := math.Trunc(val) + trunc := softfloat.Ftrunc64(tv.GetFloat64()) - if val != trunc { + if !softfloat.Feq64(trunc, tv.GetFloat64()) { return false } - return trunc >= 0 && trunc <= math.MaxUint + truncUint64 := softfloat.F64touint64(trunc) + + return truncUint64 >= 0 && truncUint64 <= math.MaxUint }) - x := uint(tv.GetFloat64()) // XXX determinism? + x := uint(softfloat.F64touint64(tv.GetFloat64())) tv.T = t tv.SetUint(x) case Uint8Kind: validate(Float64Kind, Uint8Kind, func() bool { - val := tv.GetFloat64() - trunc := math.Trunc(val) + trunc := softfloat.Ftrunc64(tv.GetFloat64()) - if val != trunc { + if !softfloat.Feq64(trunc, tv.GetFloat64()) { return false } - return int64(trunc) >= 0 && int64(trunc) <= math.MaxUint8 + truncUint64 := softfloat.F64touint64(trunc) + return truncUint64 >= 0 && truncUint64 <= math.MaxUint8 }) - x := uint8(tv.GetFloat64()) // XXX determinism? + x := uint8(softfloat.F64touint64(tv.GetFloat64())) tv.T = t tv.SetUint8(x) case Uint16Kind: validate(Float64Kind, Uint16Kind, func() bool { - val := tv.GetFloat64() - trunc := math.Trunc(val) + trunc := softfloat.Ftrunc64(tv.GetFloat64()) - if val != trunc { + if !softfloat.Feq64(trunc, tv.GetFloat64()) { return false } - return int64(trunc) >= 0 && int64(trunc) <= math.MaxUint16 + truncUint64 := softfloat.F64touint64(trunc) + return truncUint64 >= 0 && truncUint64 <= math.MaxUint16 }) - x := uint16(tv.GetFloat64()) // XXX determinism? + x := uint16(softfloat.F64touint64(tv.GetFloat64())) tv.T = t tv.SetUint16(x) case Uint32Kind: validate(Float64Kind, Uint32Kind, func() bool { - val := tv.GetFloat64() - trunc := math.Trunc(val) - - if val != trunc { + trunc := softfloat.Ftrunc64(tv.GetFloat64()) + if !softfloat.Feq64(trunc, tv.GetFloat64()) { return false } - return int64(trunc) >= 0 && int64(trunc) <= math.MaxUint32 + truncUint64 := softfloat.F64touint64(trunc) + return truncUint64 >= 0 && truncUint64 <= math.MaxUint32 }) - x := uint32(tv.GetFloat64()) // XXX determinism? + x := uint32(softfloat.F64touint64(tv.GetFloat64())) tv.T = t tv.SetUint32(x) case Uint64Kind: validate(Float64Kind, Uint64Kind, func() bool { - val := tv.GetFloat64() - trunc := math.Trunc(val) + trunc := softfloat.Ftrunc64(tv.GetFloat64()) - if val != trunc { + if !softfloat.Feq64(tv.GetFloat64(), trunc) { return false } - return trunc >= 0 && trunc <= math.MaxUint64 + truncUint64 := softfloat.F64touint64(trunc) + return truncUint64 >= 0 && truncUint64 <= math.MaxUint64 }) - x := uint64(tv.GetFloat64()) // XXX determinism? + x := softfloat.F64touint64(tv.GetFloat64()) tv.T = t tv.SetUint64(x) case Float32Kind: validate(Float64Kind, Float32Kind, func() bool { - return tv.GetFloat64() <= math.MaxFloat32 + return softfloat.Fle64(tv.GetFloat64(), math.Float64bits(float64(math.MaxFloat32))) }) - x := float32(tv.GetFloat64()) // XXX determinism? + x := softfloat.F64to32(tv.GetFloat64()) tv.T = t tv.SetFloat32(x) case Float64Kind: - x := tv.GetFloat64() // XXX determinism? + x := tv.GetFloat64() // ??? tv.T = t tv.SetFloat64(x) default: @@ -1481,7 +1481,7 @@ func ConvertUntypedBigintTo(dst *TypedValue, bv BigintValue, t Type) { if f32 == 0 && (acc == big.Below || acc == big.Above) { panic("bigint underflows float32 (too close to zero)") } - dst.SetFloat32(f32) + dst.SetFloat32(math.Float32bits(f32)) return // done case Float64Kind: dst.T = t @@ -1495,7 +1495,7 @@ func ConvertUntypedBigintTo(dst *TypedValue, bv BigintValue, t Type) { if f64 == 0 && (acc == big.Below || acc == big.Above) { panic("bigint underflows float64 (too close to zero)") } - dst.SetFloat64(f64) + dst.SetFloat64(math.Float64bits(f64)) return // done case BigdecKind: dst.T = t @@ -1610,7 +1610,7 @@ func ConvertUntypedBigdecTo(dst *TypedValue, bv BigdecValue, t Type) { dst.T = Float64Type dst.V = nil f, _ := bd.Float64() - dst.SetFloat64(f) + dst.SetFloat64(math.Float64bits(f)) return case IntKind, Int8Kind, Int16Kind, Int32Kind, Int64Kind: fallthrough @@ -1636,7 +1636,7 @@ func ConvertUntypedBigdecTo(dst *TypedValue, bv BigdecValue, t Type) { if math.IsInf(float64(f32), 0) { panic("cannot convert untyped bigdec to float32 -- too close to +-Inf") } - dst.SetFloat32(f32) + dst.SetFloat32(math.Float32bits(f32)) return case Float64Kind: dst.T = t @@ -1648,7 +1648,7 @@ func ConvertUntypedBigdecTo(dst *TypedValue, bv BigdecValue, t Type) { if math.IsInf(f64, 0) { panic("cannot convert untyped bigdec to float64 -- too close to +-Inf") } - dst.SetFloat64(f64) + dst.SetFloat64(math.Float64bits(f64)) return default: panic(fmt.Sprintf( diff --git a/gnovm/pkg/gnolang/values_conversions_test.go b/gnovm/pkg/gnolang/values_conversions_test.go index 7ffa3e98c71..5538e973bdc 100644 --- a/gnovm/pkg/gnolang/values_conversions_test.go +++ b/gnovm/pkg/gnolang/values_conversions_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/cockroachdb/apd/v3" + "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat" "github.com/stretchr/testify/require" ) @@ -24,7 +25,7 @@ func TestConvertUntypedBigdecToFloat(t *testing.T) { ConvertUntypedBigdecTo(dst, bd, typ) - require.Equal(t, float64(0), dst.GetFloat64()) + require.True(t, softfloat.Feq64(dst.GetFloat64(), 0)) } func TestBitShiftingOverflow(t *testing.T) { diff --git a/gnovm/pkg/gnolang/values_string.go b/gnovm/pkg/gnolang/values_string.go index a414f440e4e..fdf0c8f55de 100644 --- a/gnovm/pkg/gnolang/values_string.go +++ b/gnovm/pkg/gnolang/values_string.go @@ -2,6 +2,7 @@ package gnolang import ( "fmt" + "math" "reflect" "strconv" "strings" @@ -339,9 +340,9 @@ func (tv *TypedValue) ProtectedSprint(seen *seenValues, considerDeclaredType boo case Uint64Type: return fmt.Sprintf("%d", tv.GetUint64()) case Float32Type: - return fmt.Sprintf("%v", tv.GetFloat32()) + return fmt.Sprintf("%v", math.Float32frombits(tv.GetFloat32())) case Float64Type: - return fmt.Sprintf("%v", tv.GetFloat64()) + return fmt.Sprintf("%v", math.Float64frombits(tv.GetFloat64())) case UntypedBigintType, BigintType: return tv.V.(BigintValue).V.String() case UntypedBigdecType, BigdecType: @@ -447,9 +448,9 @@ func (tv TypedValue) ProtectedString(seen *seenValues) string { case Uint64Type: vs = fmt.Sprintf("%d", tv.GetUint64()) case Float32Type: - vs = fmt.Sprintf("%v", tv.GetFloat32()) + vs = fmt.Sprintf("%v", math.Float32frombits(tv.GetFloat32())) case Float64Type: - vs = fmt.Sprintf("%v", tv.GetFloat64()) + vs = fmt.Sprintf("%v", math.Float64frombits(tv.GetFloat64())) // Complex types that require recusion protection. default: vs = nilStr diff --git a/gnovm/tests/files/float8.gno b/gnovm/tests/files/float8.gno new file mode 100644 index 00000000000..989d1b4fd61 --- /dev/null +++ b/gnovm/tests/files/float8.gno @@ -0,0 +1,166 @@ +package main + +import "math" + +func main() { + asVars() + asConsts() +} + +func asVars() { + var i8 int8 = 127 + var i16 int16 = 32767 + var i32 int32 = 2147483647 + var i64 int64 = 9223372036854775807 + var i int = 9223372036854775807 + var u8 uint8 = 255 + var u16 uint16 = 65535 + var u32 uint32 = 4294967295 + var u64 uint64 = 18446744073709551615 + var f32Max float32 = math.MaxFloat32 + var f64Max float64 = math.MaxFloat64 + var f64Min float64 = math.SmallestNonzeroFloat64 + var f32Min float32 = math.SmallestNonzeroFloat32 + println(f32Max / 2) + println(f64Max / 2) + println((f32Max - 1) + 1) + println((f64Max - 1) + 1) + println((f32Max / 2) * 2) + println((f64Max / 2) * 2) + println(f32Max - 1) + println(f64Max - 1) + println(f64Min / 2) + println(f32Min / 2) + println(float32(i8)) + println(float64(i8)) + println(float32(i16)) + println(float64(i16)) + println(float32(i32)) + println(float64(i32)) + println(float32(i64)) + println(float64(i64)) + println(float32(i)) + println(float64(i)) + println(float32(u8)) + println(float64(u8)) + println(float32(u16)) + println(float64(u16)) + println(float32(u32)) + println(float64(u32)) + println(float32(u64)) + println(float64(u64)) + println(float32(f32Max)) + println(float64(f32Max)) + println(float64(f64Max)) +} + +func asConsts() { + const i8 int8 = 127 + const i16 int16 = 32767 + const i32 int32 = 2147483647 + const i64 int64 = 9223372036854775807 + const i int = 9223372036854775807 + const u8 uint8 = 255 + const u16 uint16 = 65535 + const u32 uint32 = 4294967295 + const u64 uint64 = 18446744073709551615 + const f32Max float32 = math.MaxFloat32 + const f64Max float64 = math.MaxFloat64 + const f64Min float64 = math.SmallestNonzeroFloat64 + const f32Min float32 = math.SmallestNonzeroFloat32 + println(f32Max / 2) + println(f64Max / 2) + println((f32Max - 1) + 1) + println((f64Max - 1) + 1) + println((f32Max / 2) * 2) + println((f64Max / 2) * 2) + println(f32Max - 1) + println(f64Max - 1) + println(f64Min / 2) + println(f32Min / 2) + println(float32(i8)) + println(float64(i8)) + println(float32(i16)) + println(float64(i16)) + println(float32(i32)) + println(float64(i32)) + println(float32(i64)) + println(float64(i64)) + println(float32(i)) + println(float64(i)) + println(float32(u8)) + println(float64(u8)) + println(float32(u16)) + println(float64(u16)) + println(float32(u32)) + println(float64(u32)) + println(float32(u64)) + println(float64(u64)) + println(float32(f32Max)) + println(float64(f32Max)) + println(float64(f64Max)) +} + +// Output: +// 1.7014117e+38 +// 8.988465674311579e+307 +// 3.4028235e+38 +// 1.7976931348623157e+308 +// 3.4028235e+38 +// 1.7976931348623157e+308 +// 3.4028235e+38 +// 1.7976931348623157e+308 +// 0 +// 0 +// 127 +// 127 +// 32767 +// 32767 +// 2.1474836e+09 +// 2.147483647e+09 +// 9.223372e+18 +// 9.223372036854776e+18 +// 9.223372e+18 +// 9.223372036854776e+18 +// 255 +// 255 +// 65535 +// 65535 +// 4.2949673e+09 +// 4.294967295e+09 +// 1.8446744e+19 +// 1.8446744073709552e+19 +// 3.4028235e+38 +// 3.4028234663852886e+38 +// 1.7976931348623157e+308 +// 1.7014117e+38 +// 8.988465674311579e+307 +// 3.4028235e+38 +// 1.7976931348623157e+308 +// 3.4028235e+38 +// 1.7976931348623157e+308 +// 3.4028235e+38 +// 1.7976931348623157e+308 +// 0 +// 0 +// 127 +// 127 +// 32767 +// 32767 +// 2.1474836e+09 +// 2.147483647e+09 +// 9.223372e+18 +// 9.223372036854776e+18 +// 9.223372e+18 +// 9.223372036854776e+18 +// 255 +// 255 +// 65535 +// 65535 +// 4.2949673e+09 +// 4.294967295e+09 +// 1.8446744e+19 +// 1.8446744073709552e+19 +// 3.4028235e+38 +// 3.4028234663852886e+38 +// 1.7976931348623157e+308 From ee8dcc6f2180fea94c17da38c9bcaf519a45dd7b Mon Sep 17 00:00:00 2001 From: Morgan Date: Mon, 13 Jan 2025 13:02:43 +0100 Subject: [PATCH 04/20] feat(github-bot): config improvements (#3466) - change the "docs" merge requirements to allow also for a third-party contribution approved by both core+devrel - disallow merging on all prs with the "don't merge" label - don't require additional information on dependabot PRs --- contribs/github-bot/internal/config/config.go | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/contribs/github-bot/internal/config/config.go b/contribs/github-bot/internal/config/config.go index 86acc6cfa83..f80fc86cb11 100644 --- a/contribs/github-bot/internal/config/config.go +++ b/contribs/github-bot/internal/config/config.go @@ -38,17 +38,22 @@ func Config(gh *client.GitHub) ([]AutomaticCheck, []ManualCheck) { { Description: "Changes to 'docs' folder must be reviewed/authored by at least one devrel and one tech-staff", If: c.FileChanged(gh, "^docs/"), - Then: r.Or( - r.And( - r.AuthorInTeam(gh, "devrels"), + Then: r.And( + r.Or( + r.AuthorInTeam(gh, "tech-staff"), r.ReviewByTeamMembers(gh, "tech-staff", 1), ), - r.And( - r.AuthorInTeam(gh, "tech-staff"), + r.Or( + r.AuthorInTeam(gh, "devrels"), r.ReviewByTeamMembers(gh, "devrels", 1), ), ), }, + { + Description: "Must not contain the \"don't merge\" label", + If: c.Label("don't merge"), + Then: r.Never(), + }, } manual := []ManualCheck{ @@ -59,8 +64,11 @@ func Config(gh *client.GitHub) ([]AutomaticCheck, []ManualCheck) { }, { Description: "The pull request description provides enough details", - If: c.Not(c.AuthorInTeam(gh, "core-contributors")), - Teams: Teams{"core-contributors"}, + If: c.And( + c.Not(c.AuthorInTeam(gh, "core-contributors")), + c.Not(c.Author("dependabot[bot]")), + ), + Teams: Teams{"core-contributors"}, }, { Description: "Determine if infra needs to be updated before merging", From cc5cb36fe65fb3d4f2d8cb0df2dc40f686fb55d3 Mon Sep 17 00:00:00 2001 From: Miguel Victoria Villaquiran Date: Mon, 13 Jan 2025 13:31:45 +0100 Subject: [PATCH 05/20] feat(stdlibs): add package crypto/bech32 (#3375) Related to #1475 --------- Co-authored-by: Nathan Toups <612924+n2p5@users.noreply.github.com> --- .../gno.land/p/demo/testutils/crypto_test.gno | 12 + .../r/demo/keystore/keystore_test.gno | 6 +- .../r/demo/microblog/microblog_test.gno | 2 +- .../testdata/assertorigincall.txtar | 2 +- .../testdata/grc20_invalid_address.txtar | 2 +- .../integration/testdata/grc20_registry.txtar | 4 +- .../integration/testdata/grc721_emit.txtar | 10 +- .../pkg/integration/testdata/issue_1786.txtar | 8 +- .../pkg/integration/testdata/prevrealm.txtar | 2 +- .../pkg/integration/testdata/wugnot.txtar | 14 +- gnovm/stdlibs/crypto/bech32/bech32.gno | 445 +++++++++++ gnovm/stdlibs/crypto/bech32/bech32_test.gno | 691 ++++++++++++++++++ gnovm/stdlibs/crypto/bech32/error.gno | 83 +++ gnovm/stdlibs/crypto/bech32/version.gno | 43 ++ gnovm/stdlibs/generated.go | 101 +-- gnovm/stdlibs/std/crypto.gno | 67 ++ gnovm/stdlibs/std/crypto_test.gno | 19 + gnovm/stdlibs/std/native.gno | 15 - gnovm/stdlibs/std/native.go | 21 - gnovm/tests/files/std5.gno | 2 +- gnovm/tests/files/std8.gno | 2 +- gnovm/tests/files/zrealm_natbind0.gno | 2 +- 22 files changed, 1391 insertions(+), 162 deletions(-) create mode 100644 examples/gno.land/p/demo/testutils/crypto_test.gno create mode 100644 gnovm/stdlibs/crypto/bech32/bech32.gno create mode 100644 gnovm/stdlibs/crypto/bech32/bech32_test.gno create mode 100644 gnovm/stdlibs/crypto/bech32/error.gno create mode 100644 gnovm/stdlibs/crypto/bech32/version.gno diff --git a/examples/gno.land/p/demo/testutils/crypto_test.gno b/examples/gno.land/p/demo/testutils/crypto_test.gno new file mode 100644 index 00000000000..ac77b76dadf --- /dev/null +++ b/examples/gno.land/p/demo/testutils/crypto_test.gno @@ -0,0 +1,12 @@ +package testutils + +import ( + "testing" + + "gno.land/p/demo/uassert" +) + +func TestTestAddress(t *testing.T) { + testAddr := TestAddress("author1") + uassert.Equal(t, "g1v96hg6r0wgc47h6lta047h6lta047h6lm33tq6", string(testAddr)) +} diff --git a/examples/gno.land/r/demo/keystore/keystore_test.gno b/examples/gno.land/r/demo/keystore/keystore_test.gno index ffd8e60936f..41597016ea3 100644 --- a/examples/gno.land/r/demo/keystore/keystore_test.gno +++ b/examples/gno.land/r/demo/keystore/keystore_test.gno @@ -11,7 +11,11 @@ import ( ) func TestRender(t *testing.T) { - const ( + // https://github.com/gnolang/gno/pull/3375 changed const -> var : + // For some reason non native functions fails on constants with + // constant overflows (code=2), this does not happens when using a variable + // TODO: check this issue after and if this PR is merged + var ( author1 std.Address = testutils.TestAddress("author1") author2 std.Address = testutils.TestAddress("author2") ) diff --git a/examples/gno.land/r/demo/microblog/microblog_test.gno b/examples/gno.land/r/demo/microblog/microblog_test.gno index a3c8f04ee7f..9ad98d3cbfe 100644 --- a/examples/gno.land/r/demo/microblog/microblog_test.gno +++ b/examples/gno.land/r/demo/microblog/microblog_test.gno @@ -10,7 +10,7 @@ import ( ) func TestMicroblog(t *testing.T) { - const ( + var ( author1 std.Address = testutils.TestAddress("author1") author2 std.Address = testutils.TestAddress("author2") ) diff --git a/gno.land/pkg/integration/testdata/assertorigincall.txtar b/gno.land/pkg/integration/testdata/assertorigincall.txtar index 2c4a27f9d06..2c5da25c0aa 100644 --- a/gno.land/pkg/integration/testdata/assertorigincall.txtar +++ b/gno.land/pkg/integration/testdata/assertorigincall.txtar @@ -43,7 +43,7 @@ gnokey maketx call -pkgpath gno.land/r/myrlm -func B -gas-fee 100000ugnot -gas-w stdout 'OK!' ## 3. MsgCall -> myrlm.C: PASS -gnokey maketx call -pkgpath gno.land/r/myrlm -func C -gas-fee 100000ugnot -gas-wanted 700000 -broadcast -chainid tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/myrlm -func C -gas-fee 100000ugnot -gas-wanted 1500000 -broadcast -chainid tendermint_test test1 stdout 'OK!' ## 4. MsgCall -> r/foo.A -> myrlm.A: PANIC diff --git a/gno.land/pkg/integration/testdata/grc20_invalid_address.txtar b/gno.land/pkg/integration/testdata/grc20_invalid_address.txtar index d3dcc86725c..0068384903e 100644 --- a/gno.land/pkg/integration/testdata/grc20_invalid_address.txtar +++ b/gno.land/pkg/integration/testdata/grc20_invalid_address.txtar @@ -4,7 +4,7 @@ loadpkg gno.land/r/demo/foo20 gnoland start # execute Faucet -gnokey maketx call -pkgpath gno.land/r/demo/foo20 -func Faucet -gas-fee 1000000ugnot -gas-wanted 4000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/foo20 -func Faucet -gas-fee 10000000ugnot -gas-wanted 40000000 -broadcast -chainid=tendermint_test test1 stdout 'OK!' # execute Transfer for invalid address diff --git a/gno.land/pkg/integration/testdata/grc20_registry.txtar b/gno.land/pkg/integration/testdata/grc20_registry.txtar index df11e92f8db..4377e10a575 100644 --- a/gno.land/pkg/integration/testdata/grc20_registry.txtar +++ b/gno.land/pkg/integration/testdata/grc20_registry.txtar @@ -6,7 +6,7 @@ loadpkg gno.land/r/registry $WORK/registry gnoland start # we call Transfer with foo20, before it's registered -gnokey maketx call -pkgpath gno.land/r/registry -func TransferByName -args 'foo20' -args 'g123456789' -args '42' -gas-fee 1000000ugnot -gas-wanted 150000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/registry -func TransferByName -args 'foo20' -args 'g123456789' -args '42' -gas-fee 1000000ugnot -gas-wanted 1500000 -broadcast -chainid=tendermint_test test1 stdout 'not found' # add foo20, and foo20wrapper @@ -14,7 +14,7 @@ gnokey maketx addpkg -pkgdir $WORK/foo20 -pkgpath gno.land/r/foo20 -gas-fee 1000 gnokey maketx addpkg -pkgdir $WORK/foo20wrapper -pkgpath gno.land/r/foo20wrapper -gas-fee 1000000ugnot -gas-wanted 10000000 -broadcast -chainid=tendermint_test test1 # we call Transfer with foo20, after it's registered -gnokey maketx call -pkgpath gno.land/r/registry -func TransferByName -args 'foo20' -args 'g123456789' -args '42' -gas-fee 1000000ugnot -gas-wanted 800000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/registry -func TransferByName -args 'foo20' -args 'g123456789' -args '42' -gas-fee 1000000ugnot -gas-wanted 8000000 -broadcast -chainid=tendermint_test test1 stdout 'same address, success!' -- registry/registry.gno -- diff --git a/gno.land/pkg/integration/testdata/grc721_emit.txtar b/gno.land/pkg/integration/testdata/grc721_emit.txtar index 6b4770e37c6..45101b74634 100644 --- a/gno.land/pkg/integration/testdata/grc721_emit.txtar +++ b/gno.land/pkg/integration/testdata/grc721_emit.txtar @@ -6,23 +6,23 @@ loadpkg gno.land/r/foo721 $WORK/foo721 gnoland start # Mint -gnokey maketx call -pkgpath gno.land/r/foo721 -func Mint -args g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 -args 1 -gas-fee 1000000ugnot -gas-wanted 3500000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/foo721 -func Mint -args g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 -args 1 -gas-fee 1000000ugnot -gas-wanted 35000000 -broadcast -chainid=tendermint_test test1 stdout '\[{\"type\":\"Mint\",\"attrs\":\[{\"key\":\"to\",\"value\":\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\"},{\"key\":\"tokenId\",\"value\":\"1\"}],\"pkg_path\":\"gno.land\/r\/foo721\",\"func\":\"mint\"}\]' # Approve -gnokey maketx call -pkgpath gno.land/r/foo721 -func Approve -args g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj -args 1 -gas-fee 1000000ugnot -gas-wanted 3500000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/foo721 -func Approve -args g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj -args 1 -gas-fee 1000000ugnot -gas-wanted 35000000 -broadcast -chainid=tendermint_test test1 stdout '\[{\"type\":\"Approval\",\"attrs\":\[{\"key\":\"owner\",\"value\":\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\"},{\"key\":\"to\",\"value\":\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\"},{\"key\":\"tokenId\",\"value\":\"1\"}],\"pkg_path\":\"gno.land\/r\/foo721\",\"func\":\"Approve\"}\]' # SetApprovalForAll -gnokey maketx call -pkgpath gno.land/r/foo721 -func SetApprovalForAll -args g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj -args false -gas-fee 1000000ugnot -gas-wanted 3500000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/foo721 -func SetApprovalForAll -args g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj -args false -gas-fee 1000000ugnot -gas-wanted 35000000 -broadcast -chainid=tendermint_test test1 stdout '\[{\"type\":\"ApprovalForAll\",\"attrs\":\[{\"key\":\"owner\",\"value\":\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\"},{\"key\":\"to\",\"value\":\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\"},{\"key\":\"approved\",\"value\":\"false\"}],\"pkg_path\":\"gno\.land/r/foo721\",\"func\":\"setApprovalForAll\"}\]' # TransferFrom -gnokey maketx call -pkgpath gno.land/r/foo721 -func TransferFrom -args g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 -args g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj -args 1 -gas-fee 1000000ugnot -gas-wanted 3500000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/foo721 -func TransferFrom -args g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 -args g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj -args 1 -gas-fee 1000000ugnot -gas-wanted 35000000 -broadcast -chainid=tendermint_test test1 stdout '\[{\"type\":\"Transfer\",\"attrs\":\[{\"key\":\"from\",\"value\":\"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5\"},{\"key\":\"to\",\"value\":\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\"},{\"key\":\"tokenId\",\"value\":\"1\"}],\"pkg_path\":\"gno\.land/r/foo721\",\"func\":\"transfer\"}\]' # Burn -gnokey maketx call -pkgpath gno.land/r/foo721 -func Burn -args 1 -gas-fee 1000000ugnot -gas-wanted 3500000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/foo721 -func Burn -args 1 -gas-fee 1000000ugnot -gas-wanted 35000000 -broadcast -chainid=tendermint_test test1 stdout '\[{\"type\":\"Burn\",\"attrs\":\[{\"key\":\"from\",\"value\":\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\"},{\"key\":\"tokenId\",\"value\":\"1\"}],\"pkg_path\":\"gno\.land/r/foo721\",\"func\":\"Burn\"}\]' diff --git a/gno.land/pkg/integration/testdata/issue_1786.txtar b/gno.land/pkg/integration/testdata/issue_1786.txtar index 0e66a882a6d..1cbaf2c6643 100644 --- a/gno.land/pkg/integration/testdata/issue_1786.txtar +++ b/gno.land/pkg/integration/testdata/issue_1786.txtar @@ -9,20 +9,20 @@ gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/demo/proxywugnot -gas-fee stdout OK! # approve wugnot to `proxywugnot ≈ g1fndyg0we60rdfchyy5dwxzkfmhl5u34j932rg3` -gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Approve -args "g1fndyg0we60rdfchyy5dwxzkfmhl5u34j932rg3" -args 10000 -gas-fee 1000000ugnot -gas-wanted 4000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Approve -args "g1fndyg0we60rdfchyy5dwxzkfmhl5u34j932rg3" -args 10000 -gas-fee 1000000ugnot -gas-wanted 40000000 -broadcast -chainid=tendermint_test test1 stdout OK! # send 10000ugnot to `proxywugnot` to wrap it -gnokey maketx call -pkgpath gno.land/r/demo/proxywugnot --send "10000ugnot" -func ProxyWrap -gas-fee 1000000ugnot -gas-wanted 4000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/proxywugnot --send "10000ugnot" -func ProxyWrap -gas-fee 1000000ugnot -gas-wanted 40000000 -broadcast -chainid=tendermint_test test1 stdout OK! # check user's wugnot balance -gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func BalanceOf -args "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" -gas-fee 1000000ugnot -gas-wanted 4000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func BalanceOf -args "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" -gas-fee 1000000ugnot -gas-wanted 40000000 -broadcast -chainid=tendermint_test test1 stdout OK! stdout '10000 uint64' # unwrap 500 wugnot -gnokey maketx call -pkgpath gno.land/r/demo/proxywugnot -func ProxyUnwrap -args 500 -gas-fee 1000000ugnot -gas-wanted 4000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/proxywugnot -func ProxyUnwrap -args 500 -gas-fee 1000000ugnot -gas-wanted 40000000 -broadcast -chainid=tendermint_test test1 # XXX without patching anything it will panic # panic msg: insufficient coins error diff --git a/gno.land/pkg/integration/testdata/prevrealm.txtar b/gno.land/pkg/integration/testdata/prevrealm.txtar index 4bbe16c3205..31f0ca336ba 100644 --- a/gno.land/pkg/integration/testdata/prevrealm.txtar +++ b/gno.land/pkg/integration/testdata/prevrealm.txtar @@ -34,7 +34,7 @@ env RFOO_USER_ADDR=g1evezrh92xaucffmtgsaa3rvmz5s8kedffsg469 # Test cases ## 1. MsgCall -> myrlm.A: user address -gnokey maketx call -pkgpath gno.land/r/myrlm -func A -gas-fee 100000ugnot -gas-wanted 700000 -broadcast -chainid tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/myrlm -func A -gas-fee 100000ugnot -gas-wanted 1500000 -broadcast -chainid tendermint_test test1 stdout ${test1_user_addr} ## 2. MsgCall -> myrealm.B -> myrlm.A: user address diff --git a/gno.land/pkg/integration/testdata/wugnot.txtar b/gno.land/pkg/integration/testdata/wugnot.txtar index 5fa7dab2945..5a63e0148e2 100644 --- a/gno.land/pkg/integration/testdata/wugnot.txtar +++ b/gno.land/pkg/integration/testdata/wugnot.txtar @@ -2,14 +2,14 @@ loadpkg gno.land/r/demo/wugnot gnoland start -gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Render -gas-fee 1000000ugnot -gas-wanted 5000000 -args '' -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Render -gas-fee 10000000ugnot -gas-wanted 5000000 -args '' -broadcast -chainid=tendermint_test test1 stdout '# wrapped GNOT \(\$wugnot\)' stdout 'Decimals..: 0' stdout 'Total supply..: 0' stdout 'Known accounts..: 0' stdout 'OK!' -gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Deposit -send 12345678ugnot -gas-fee 1000000ugnot -gas-wanted 5000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Deposit -send 12345678ugnot -gas-fee 1000000ugnot -gas-wanted 50000000 -broadcast -chainid=tendermint_test test1 stdout 'OK!' gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Render -gas-fee 1000000ugnot -gas-wanted 5000000 -args '' -broadcast -chainid=tendermint_test test1 @@ -18,7 +18,7 @@ stdout 'Known accounts..: 1' stdout 'OK!' # XXX: use test2 instead (depends on https://github.com/gnolang/gno/issues/1269#issuecomment-1806386069) -gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Deposit -send 12345678ugnot -gas-fee 1000000ugnot -gas-wanted 5000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Deposit -send 12345678ugnot -gas-fee 1000000ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 stdout 'OK!' gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Render -gas-fee 1000000ugnot -gas-wanted 5000000 -args '' -broadcast -chainid=tendermint_test test1 @@ -27,19 +27,19 @@ stdout 'Known accounts..: 1' # should be 2 once we can use test2 stdout 'OK!' # XXX: replace hardcoded address with test3 -gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Transfer -gas-fee 1000000ugnot -gas-wanted 5000000 -args 'g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq' -args '10000000' -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Transfer -gas-fee 1000000ugnot -gas-wanted 100000000 -args 'g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq' -args '10000000' -broadcast -chainid=tendermint_test test1 stdout 'OK!' -gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Render -gas-fee 1000000ugnot -gas-wanted 5000000 -args '' -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Render -gas-fee 1000000ugnot -gas-wanted 50000000 -args '' -broadcast -chainid=tendermint_test test1 stdout 'Total supply..: 24691356' stdout 'Known accounts..: 2' # should be 3 once we can use test2 stdout 'OK!' # XXX: use test3 instead (depends on https://github.com/gnolang/gno/issues/1269#issuecomment-1806386069) -gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Withdraw -args 10000000 -gas-fee 1000000ugnot -gas-wanted 5000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Withdraw -args 10000000 -gas-fee 1000000ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 stdout 'OK!' -gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Render -gas-fee 1000000ugnot -gas-wanted 5000000 -args '' -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Render -gas-fee 1000000ugnot -gas-wanted 100000000 -args '' -broadcast -chainid=tendermint_test test1 stdout 'Total supply..: 14691356' stdout 'Known accounts..: 2' # should be 3 once we can use test2 stdout 'OK!' diff --git a/gnovm/stdlibs/crypto/bech32/bech32.gno b/gnovm/stdlibs/crypto/bech32/bech32.gno new file mode 100644 index 00000000000..92994b28813 --- /dev/null +++ b/gnovm/stdlibs/crypto/bech32/bech32.gno @@ -0,0 +1,445 @@ +// Copyright (c) 2017 The btcsuite developers +// Copyright (c) 2019 The Decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package bech32 + +import ( + "strings" +) + +// charset is the set of characters used in the data section of bech32 strings. +// Note that this is ordered, such that for a given charset[i], i is the binary +// value of the character. +const charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" + +// gen encodes the generator polynomial for the bech32 BCH checksum. +var gen = []int{0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3} + +// toBytes converts each character in the string 'chars' to the value of the +// index of the corresponding character in 'charset'. +func toBytes(chars string) ([]byte, error) { + decoded := make([]byte, 0, len(chars)) + for i := 0; i < len(chars); i++ { + index := strings.IndexByte(charset, chars[i]) + if index < 0 { + return nil, ErrNonCharsetChar(chars[i]) + } + decoded = append(decoded, byte(index)) + } + return decoded, nil +} + +// bech32Polymod calculates the BCH checksum for a given hrp, values and +// checksum data. Checksum is optional, and if nil a 0 checksum is assumed. +// +// Values and checksum (if provided) MUST be encoded as 5 bits per element (base +// 32), otherwise the results are undefined. +// +// For more details on the polymod calculation, please refer to BIP 173. +func bech32Polymod(hrp string, values, checksum []byte) int { + chk := 1 + + // Account for the high bits of the HRP in the checksum. + for i := 0; i < len(hrp); i++ { + b := chk >> 25 + hiBits := int(hrp[i]) >> 5 + chk = (chk&0x1ffffff)<<5 ^ hiBits + for i := 0; i < 5; i++ { + if (b>>uint(i))&1 == 1 { + chk ^= gen[i] + } + } + } + + // Account for the separator (0) between high and low bits of the HRP. + // x^0 == x, so we eliminate the redundant xor used in the other rounds. + b := chk >> 25 + chk = (chk & 0x1ffffff) << 5 + for i := 0; i < 5; i++ { + if (b>>uint(i))&1 == 1 { + chk ^= gen[i] + } + } + + // Account for the low bits of the HRP. + for i := 0; i < len(hrp); i++ { + b := chk >> 25 + loBits := int(hrp[i]) & 31 + chk = (chk&0x1ffffff)<<5 ^ loBits + for i := 0; i < 5; i++ { + if (b>>uint(i))&1 == 1 { + chk ^= gen[i] + } + } + } + + // Account for the values. + for _, v := range values { + b := chk >> 25 + chk = (chk&0x1ffffff)<<5 ^ int(v) + for i := 0; i < 5; i++ { + if (b>>uint(i))&1 == 1 { + chk ^= gen[i] + } + } + } + + if checksum == nil { + // A nil checksum is used during encoding, so assume all bytes are zero. + // x^0 == x, so we eliminate the redundant xor used in the other rounds. + for v := 0; v < 6; v++ { + b := chk >> 25 + chk = (chk & 0x1ffffff) << 5 + for i := 0; i < 5; i++ { + if (b>>uint(i))&1 == 1 { + chk ^= gen[i] + } + } + } + } else { + // Checksum is provided during decoding, so use it. + for _, v := range checksum { + b := chk >> 25 + chk = (chk&0x1ffffff)<<5 ^ int(v) + for i := 0; i < 5; i++ { + if (b>>uint(i))&1 == 1 { + chk ^= gen[i] + } + } + } + } + + return chk +} + +// writeBech32Checksum calculates the checksum data expected for a string that +// will have the given hrp and payload data and writes it to the provided string +// builder. +// +// The payload data MUST be encoded as a base 32 (5 bits per element) byte slice +// and the hrp MUST only use the allowed character set (ascii chars between 33 +// and 126), otherwise the results are undefined. +// +// For more details on the checksum calculation, please refer to BIP 173. +func writeBech32Checksum(hrp string, data []byte, bldr *strings.Builder, + version Version) { + + bech32Const := int(VersionToConsts[version]) + polymod := bech32Polymod(hrp, data, nil) ^ bech32Const + for i := 0; i < 6; i++ { + b := byte((polymod >> uint(5*(5-i))) & 31) + + // This can't fail, given we explicitly cap the previous b byte by the + // first 31 bits. + c := charset[b] + bldr.WriteByte(c) + } +} + +// bech32VerifyChecksum verifies whether the bech32 string specified by the +// provided hrp and payload data (encoded as 5 bits per element byte slice) has +// the correct checksum suffix. The version of bech32 used (bech32 OG, or +// bech32m) is also returned to allow the caller to perform proper address +// validation (segwitv0 should use bech32, v1+ should use bech32m). +// +// Data MUST have more than 6 elements, otherwise this function panics. +// +// For more details on the checksum verification, please refer to BIP 173. +func bech32VerifyChecksum(hrp string, data []byte) (Version, bool) { + checksum := data[len(data)-6:] + values := data[:len(data)-6] + polymod := bech32Polymod(hrp, values, checksum) + + // Before BIP-350, we'd always check this against a static constant of + // 1 to know if the checksum was computed properly. As we want to + // generically support decoding for bech32m as well as bech32, we'll + // look up the returned value and compare it to the set of defined + // constants. + bech32Version, ok := ConstsToVersion[ChecksumConst(polymod)] + if ok { + return bech32Version, true + } + + return VersionUnknown, false +} + +// DecodeNoLimitWithVersion is a bech32 checksum version aware arbitrary string +// length decoder. This function will return the version of the decoded +// checksum constant so higher level validation can be performed to ensure the +// correct version of bech32 was used when encoding. +// +// Note that the returned data is 5-bit (base32) encoded and the human-readable +// part will be lowercase. +func DecodeNoLimitWithVersion(bech string) (string, []byte, Version, error) { + // The minimum allowed size of a bech32 string is 8 characters, since it + // needs a non-empty HRP, a separator, and a 6 character checksum. + if len(bech) < 8 { + return "", nil, VersionUnknown, ErrInvalidLength(len(bech)) + } + + // Only ASCII characters between 33 and 126 are allowed. + var hasLower, hasUpper bool + for i := 0; i < len(bech); i++ { + if bech[i] < 33 || bech[i] > 126 { + return "", nil, VersionUnknown, ErrInvalidCharacter(bech[i]) + } + + // The characters must be either all lowercase or all uppercase. Testing + // directly with ascii codes is safe here, given the previous test. + hasLower = hasLower || (bech[i] >= 97 && bech[i] <= 122) + hasUpper = hasUpper || (bech[i] >= 65 && bech[i] <= 90) + if hasLower && hasUpper { + return "", nil, VersionUnknown, ErrMixedCase{} + } + } + + // Bech32 standard uses only the lowercase for of strings for checksum + // calculation. + if hasUpper { + bech = strings.ToLower(bech) + } + + // The string is invalid if the last '1' is non-existent, it is the + // first character of the string (no human-readable part) or one of the + // last 6 characters of the string (since checksum cannot contain '1'). + one := strings.LastIndexByte(bech, '1') + if one < 1 || one+7 > len(bech) { + return "", nil, VersionUnknown, ErrInvalidSeparatorIndex(one) + } + + // The human-readable part is everything before the last '1'. + hrp := bech[:one] + data := bech[one+1:] + + // Each character corresponds to the byte with value of the index in + // 'charset'. + decoded, err := toBytes(data) + if err != nil { + return "", nil, VersionUnknown, err + } + + // Verify if the checksum (stored inside decoded[:]) is valid, given the + // previously decoded hrp. + bech32Version, ok := bech32VerifyChecksum(hrp, decoded) + if !ok { + // Invalid checksum. Calculate what it should have been, so that the + // error contains this information. + + // Extract the payload bytes and actual checksum in the string. + actual := bech[len(bech)-6:] + payload := decoded[:len(decoded)-6] + + // Calculate the expected checksum, given the hrp and payload + // data. We'll actually compute _both_ possibly valid checksum + // to further aide in debugging. + var expectedBldr strings.Builder + expectedBldr.Grow(6) + writeBech32Checksum(hrp, payload, &expectedBldr, Version0) + expectedVersion0 := expectedBldr.String() + + var b strings.Builder + b.Grow(6) + writeBech32Checksum(hrp, payload, &expectedBldr, VersionM) + expectedVersionM := expectedBldr.String() + + err = ErrInvalidChecksum{ + Expected: expectedVersion0, + ExpectedM: expectedVersionM, + Actual: actual, + } + return "", nil, VersionUnknown, err + } + + // We exclude the last 6 bytes, which is the checksum. + return hrp, decoded[:len(decoded)-6], bech32Version, nil +} + +// DecodeNoLimit decodes a bech32 encoded string, returning the human-readable +// part and the data part excluding the checksum. This function does NOT +// validate against the BIP-173 maximum length allowed for bech32 strings and +// is meant for use in custom applications (such as lightning network payment +// requests), NOT on-chain addresses. +// +// Note that the returned data is 5-bit (base32) encoded and the human-readable +// part will be lowercase. +func DecodeNoLimit(bech string) (string, []byte, error) { + hrp, data, _, err := DecodeNoLimitWithVersion(bech) + return hrp, data, err +} + +// Decode decodes a bech32 encoded string, returning the human-readable part and +// the data part excluding the checksum. +// +// Note that the returned data is 5-bit (base32) encoded and the human-readable +// part will be lowercase. +func Decode(bech string) (string, []byte, error) { + // The maximum allowed length for a bech32 string is 90. + if len(bech) > 90 { + return "", nil, ErrInvalidLength(len(bech)) + } + + hrp, data, _, err := DecodeNoLimitWithVersion(bech) + return hrp, data, err +} + +// DecodeGeneric is identical to the existing Decode method, but will also +// return bech32 version that matches the decoded checksum. This method should +// be used when decoding segwit addresses, as it enables additional +// verification to ensure the proper checksum is used. +func DecodeGeneric(bech string) (string, []byte, Version, error) { + // The maximum allowed length for a bech32 string is 90. + if len(bech) > 90 { + return "", nil, VersionUnknown, ErrInvalidLength(len(bech)) + } + + return DecodeNoLimitWithVersion(bech) +} + +// encodeGeneric is the base bech32 encoding function that is aware of the +// existence of the checksum versions. This method is private, as the Encode +// and EncodeM methods are intended to be used instead. +func encodeGeneric(hrp string, data []byte, + version Version) (string, error) { + + // The resulting bech32 string is the concatenation of the lowercase + // hrp, the separator 1, data and the 6-byte checksum. + hrp = strings.ToLower(hrp) + var bldr strings.Builder + bldr.Grow(len(hrp) + 1 + len(data) + 6) + bldr.WriteString(hrp) + bldr.WriteString("1") + + // Write the data part, using the bech32 charset. + for _, b := range data { + if int(b) >= len(charset) { + return "", ErrInvalidDataByte(b) + } + bldr.WriteByte(charset[b]) + } + + // Calculate and write the checksum of the data. + writeBech32Checksum(hrp, data, &bldr, version) + + return bldr.String(), nil +} + +// Encode encodes a byte slice into a bech32 string with the given +// human-readable part (HRP). The HRP will be converted to lowercase if needed +// since mixed cased encodings are not permitted and lowercase is used for +// checksum purposes. Note that the bytes must each encode 5 bits (base32). +func Encode(hrp string, data []byte) (string, error) { + return encodeGeneric(hrp, data, Version0) +} + +// EncodeM is the exactly same as the Encode method, but it uses the new +// bech32m constant instead of the original one. It should be used whenever one +// attempts to encode a segwit address of v1 and beyond. +func EncodeM(hrp string, data []byte) (string, error) { + return encodeGeneric(hrp, data, VersionM) +} + +// ConvertBits converts a byte slice where each byte is encoding fromBits bits, +// to a byte slice where each byte is encoding toBits bits. +func ConvertBits(data []byte, fromBits, toBits uint8, pad bool) ([]byte, error) { + if fromBits < 1 || fromBits > 8 || toBits < 1 || toBits > 8 { + return nil, ErrInvalidBitGroups{} + } + + // Determine the maximum size the resulting array can have after base + // conversion, so that we can size it a single time. This might be off + // by a byte depending on whether padding is used or not and if the input + // data is a multiple of both fromBits and toBits, but we ignore that and + // just size it to the maximum possible. + maxSize := len(data)*int(fromBits)/int(toBits) + 1 + + // The final bytes, each byte encoding toBits bits. + regrouped := make([]byte, 0, maxSize) + + // Keep track of the next byte we create and how many bits we have + // added to it out of the toBits goal. + nextByte := byte(0) + filledBits := uint8(0) + + for _, b := range data { + + // Discard unused bits. + b <<= 8 - fromBits + + // How many bits remaining to extract from the input data. + remFromBits := fromBits + for remFromBits > 0 { + // How many bits remaining to be added to the next byte. + remToBits := toBits - filledBits + + // The number of bytes to next extract is the minimum of + // remFromBits and remToBits. + toExtract := remFromBits + if remToBits < toExtract { + toExtract = remToBits + } + + // Add the next bits to nextByte, shifting the already + // added bits to the left. + nextByte = (nextByte << toExtract) | (b >> (8 - toExtract)) + + // Discard the bits we just extracted and get ready for + // next iteration. + b <<= toExtract + remFromBits -= toExtract + filledBits += toExtract + + // If the nextByte is completely filled, we add it to + // our regrouped bytes and start on the next byte. + if filledBits == toBits { + regrouped = append(regrouped, nextByte) + filledBits = 0 + nextByte = 0 + } + } + } + + // We pad any unfinished group if specified. + if pad && filledBits > 0 { + nextByte <<= toBits - filledBits + regrouped = append(regrouped, nextByte) + filledBits = 0 + nextByte = 0 + } + + // Any incomplete group must be <= 4 bits, and all zeroes. + if filledBits > 0 && (filledBits > 4 || nextByte != 0) { + return nil, ErrInvalidIncompleteGroup{} + } + + return regrouped, nil +} + +// EncodeFromBase256 converts a base256-encoded byte slice into a base32-encoded +// byte slice and then encodes it into a bech32 string with the given +// human-readable part (HRP). The HRP will be converted to lowercase if needed +// since mixed cased encodings are not permitted and lowercase is used for +// checksum purposes. +func EncodeFromBase256(hrp string, data []byte) (string, error) { + converted, err := ConvertBits(data, 8, 5, true) + if err != nil { + return "", err + } + return Encode(hrp, converted) +} + +// DecodeToBase256 decodes a bech32-encoded string into its associated +// human-readable part (HRP) and base32-encoded data, converts that data to a +// base256-encoded byte slice and returns it along with the lowercase HRP. +func DecodeToBase256(bech string) (string, []byte, error) { + hrp, data, err := Decode(bech) + if err != nil { + return "", nil, err + } + converted, err := ConvertBits(data, 5, 8, false) + if err != nil { + return "", nil, err + } + return hrp, converted, nil +} diff --git a/gnovm/stdlibs/crypto/bech32/bech32_test.gno b/gnovm/stdlibs/crypto/bech32/bech32_test.gno new file mode 100644 index 00000000000..3f637c40345 --- /dev/null +++ b/gnovm/stdlibs/crypto/bech32/bech32_test.gno @@ -0,0 +1,691 @@ +// Copyright (c) 2017-2020 The btcsuite developers +// Copyright (c) 2019 The Decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package bech32 + +import ( + "bytes" + "encoding/hex" + "fmt" + "strings" + "testing" +) + +// TestBech32 tests whether decoding and re-encoding the valid BIP-173 test +// vectors works and if decoding invalid test vectors fails for the correct +// reason. +func TestBech32(t *testing.T) { + tests := []struct { + str string + expectedError error + }{ + {"A12UEL5L", nil}, + {"a12uel5l", nil}, + {"an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs", nil}, + {"abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", nil}, + {"11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j", nil}, + {"split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", nil}, + {"split1checkupstagehandshakeupstreamerranterredcaperred2y9e2w", ErrInvalidChecksum{"2y9e3w", "2y9e3wlc445v", "2y9e2w"}}, // invalid checksum + {"s lit1checkupstagehandshakeupstreamerranterredcaperredp8hs2p", ErrInvalidCharacter(' ')}, // invalid character (space) in hrp + {"spl\x7Ft1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", ErrInvalidCharacter(127)}, // invalid character (DEL) in hrp + {"split1cheo2y9e2w", ErrNonCharsetChar('o')}, // invalid character (o) in data part + {"split1a2y9w", ErrInvalidSeparatorIndex(5)}, // too short data part + {"1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", ErrInvalidSeparatorIndex(0)}, // empty hrp + {"11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j", ErrInvalidLength(91)}, // too long + + // Additional test vectors used in bitcoin core + {" 1nwldj5", ErrInvalidCharacter(' ')}, + {"\x7f" + "1axkwrx", ErrInvalidCharacter(0x7f)}, + {"\x801eym55h", ErrInvalidCharacter(0x80)}, + {"an84characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx", ErrInvalidLength(91)}, + {"pzry9x0s0muk", ErrInvalidSeparatorIndex(-1)}, + {"1pzry9x0s0muk", ErrInvalidSeparatorIndex(0)}, + {"x1b4n0q5v", ErrNonCharsetChar(98)}, + {"li1dgmt3", ErrInvalidSeparatorIndex(2)}, + {"de1lg7wt\xff", ErrInvalidCharacter(0xff)}, + {"A1G7SGD8", ErrInvalidChecksum{"2uel5l", "2uel5llqfn3a", "g7sgd8"}}, + {"10a06t8", ErrInvalidLength(7)}, + {"1qzzfhee", ErrInvalidSeparatorIndex(0)}, + {"a12UEL5L", ErrMixedCase{}}, + {"A12uEL5L", ErrMixedCase{}}, + } + + for i, test := range tests { + str := test.str + hrp, decoded, err := Decode(str) + if test.expectedError != err { + t.Errorf("%d: expected decoding error %v "+ + "instead got %v", i, test.expectedError, err) + continue + } + + if err != nil { + // End test case here if a decoding error was expected. + continue + } + + // Check that it encodes to the same string + encoded, err := Encode(hrp, decoded) + if err != nil { + t.Errorf("encoding failed: %v", err) + } + + if encoded != strings.ToLower(str) { + t.Errorf("expected data to encode to %v, but got %v", + str, encoded) + } + + // Flip a bit in the string an make sure it is caught. + pos := strings.LastIndexAny(str, "1") + flipped := str[:pos+1] + string((str[pos+1] ^ 1)) + str[pos+2:] + _, _, err = Decode(flipped) + if err == nil { + t.Error("expected decoding to fail") + } + } +} + +// TestBech32M tests that the following set of strings, based on the test +// vectors in BIP-350 are either valid or invalid using the new bech32m +// checksum algo. Some of these strings are similar to the set of above test +// vectors, but end up with different checksums. +func TestBech32M(t *testing.T) { + tests := []struct { + str string + expectedError error + }{ + {"A1LQFN3A", nil}, + {"a1lqfn3a", nil}, + {"an83characterlonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11sg7hg6", nil}, + {"abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx", nil}, + {"11llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllludsr8", nil}, + {"split1checkupstagehandshakeupstreamerranterredcaperredlc445v", nil}, + {"?1v759aa", nil}, + + // Additional test vectors used in bitcoin core + {"\x201xj0phk", ErrInvalidCharacter('\x20')}, + {"\x7f1g6xzxy", ErrInvalidCharacter('\x7f')}, + {"\x801vctc34", ErrInvalidCharacter('\x80')}, + {"an84characterslonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11d6pts4", ErrInvalidLength(91)}, + {"qyrz8wqd2c9m", ErrInvalidSeparatorIndex(-1)}, + {"1qyrz8wqd2c9m", ErrInvalidSeparatorIndex(0)}, + {"y1b0jsk6g", ErrNonCharsetChar(98)}, + {"lt1igcx5c0", ErrNonCharsetChar(105)}, + {"in1muywd", ErrInvalidSeparatorIndex(2)}, + {"mm1crxm3i", ErrNonCharsetChar(105)}, + {"au1s5cgom", ErrNonCharsetChar(111)}, + {"M1VUXWEZ", ErrInvalidChecksum{"mzl49c", "mzl49cw70eq6", "vuxwez"}}, + {"16plkw9", ErrInvalidLength(7)}, + {"1p2gdwpf", ErrInvalidSeparatorIndex(0)}, + + {" 1nwldj5", ErrInvalidCharacter(' ')}, + {"\x7f" + "1axkwrx", ErrInvalidCharacter(0x7f)}, + {"\x801eym55h", ErrInvalidCharacter(0x80)}, + } + + for i, test := range tests { + str := test.str + hrp, decoded, err := Decode(str) + if test.expectedError != err { + t.Errorf("%d: (%v) expected decoding error %v "+ + "instead got %v", i, str, test.expectedError, + err) + continue + } + + if err != nil { + // End test case here if a decoding error was expected. + continue + } + + // Check that it encodes to the same string, using bech32 m. + encoded, err := EncodeM(hrp, decoded) + if err != nil { + t.Errorf("encoding failed: %v", err) + } + + if encoded != strings.ToLower(str) { + t.Errorf("expected data to encode to %v, but got %v", + str, encoded) + } + + // Flip a bit in the string an make sure it is caught. + pos := strings.LastIndexAny(str, "1") + flipped := str[:pos+1] + string((str[pos+1] ^ 1)) + str[pos+2:] + _, _, err = Decode(flipped) + if err == nil { + t.Error("expected decoding to fail") + } + } +} + +// TestBech32DecodeGeneric tests that given a bech32 string, or a bech32m +// string, the proper checksum version is returned so that callers can perform +// segwit addr validation. +func TestBech32DecodeGeneric(t *testing.T) { + tests := []struct { + str string + version Version + }{ + {"A1LQFN3A", VersionM}, + {"a1lqfn3a", VersionM}, + {"an83characterlonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11sg7hg6", VersionM}, + {"abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx", VersionM}, + {"11llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllludsr8", VersionM}, + {"split1checkupstagehandshakeupstreamerranterredcaperredlc445v", VersionM}, + {"?1v759aa", VersionM}, + + {"A12UEL5L", Version0}, + {"a12uel5l", Version0}, + {"an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs", Version0}, + {"abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", Version0}, + {"11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j", Version0}, + {"split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", Version0}, + + {"BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4", Version0}, + {"tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7", Version0}, + {"bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y", VersionM}, + {"BC1SW50QGDZ25J", VersionM}, + {"bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs", VersionM}, + {"tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy", Version0}, + {"tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c", VersionM}, + {"bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0", VersionM}, + } + for i, test := range tests { + _, _, version, err := DecodeGeneric(test.str) + if err != nil { + t.Errorf("%d: (%v) unexpected error during "+ + "decoding: %v", i, test.str, err) + continue + } + + if version != test.version { + t.Errorf("(%v): invalid version: expected %v, got %v", + test.str, test.version, version) + } + } +} + +// TestMixedCaseEncode ensures mixed case HRPs are converted to lowercase as +// expected when encoding and that decoding the produced encoding when converted +// to all uppercase produces the lowercase HRP and original data. +func TestMixedCaseEncode(t *testing.T) { + tests := []struct { + name string + hrp string + data string + encoded string + }{{ + name: "all uppercase HRP with no data", + hrp: "A", + data: "", + encoded: "a12uel5l", + }, { + name: "all uppercase HRP with data", + hrp: "UPPERCASE", + data: "787878", + encoded: "uppercase10pu8sss7kmp", + }, { + name: "mixed case HRP even offsets uppercase", + hrp: "AbCdEf", + data: "00443214c74254b635cf84653a56d7c675be77df", + encoded: "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", + }, { + name: "mixed case HRP odd offsets uppercase ", + hrp: "aBcDeF", + data: "00443214c74254b635cf84653a56d7c675be77df", + encoded: "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", + }, { + name: "all lowercase HRP", + hrp: "abcdef", + data: "00443214c74254b635cf84653a56d7c675be77df", + encoded: "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", + }} + + for _, test := range tests { + // Convert the text hex to bytes, convert those bytes from base256 to + // base32, then ensure the encoded result with the HRP provided in the + // test data is as expected. + data, err := hex.DecodeString(test.data) + if err != nil { + t.Errorf("%q: invalid hex %q: %v", test.name, test.data, err) + continue + } + convertedData, err := ConvertBits(data, 8, 5, true) + if err != nil { + t.Errorf("%q: unexpected convert bits error: %v", test.name, + err) + continue + } + gotEncoded, err := Encode(test.hrp, convertedData) + if err != nil { + t.Errorf("%q: unexpected encode error: %v", test.name, err) + continue + } + if gotEncoded != test.encoded { + t.Errorf("%q: mismatched encoding -- got %q, want %q", test.name, + gotEncoded, test.encoded) + continue + } + + // Ensure the decoding the expected lowercase encoding converted to all + // uppercase produces the lowercase HRP and original data. + gotHRP, gotData, err := Decode(strings.ToUpper(test.encoded)) + if err != nil { + t.Errorf("%q: unexpected decode error: %v", test.name, err) + continue + } + wantHRP := strings.ToLower(test.hrp) + if gotHRP != wantHRP { + t.Errorf("%q: mismatched decoded HRP -- got %q, want %q", test.name, + gotHRP, wantHRP) + continue + } + convertedGotData, err := ConvertBits(gotData, 5, 8, false) + if err != nil { + t.Errorf("%q: unexpected convert bits error: %v", test.name, + err) + continue + } + if !bytes.Equal(convertedGotData, data) { + t.Errorf("%q: mismatched data -- got %x, want %x", test.name, + convertedGotData, data) + continue + } + } +} + +// TestCanDecodeUnlimitedBech32 tests whether decoding a large bech32 string works +// when using the DecodeNoLimit version +func TestCanDecodeUnlimitedBech32(t *testing.T) { + input := "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq5kx0yd" + + // Sanity check that an input of this length errors on regular Decode() + _, _, err := Decode(input) + if err == nil { + t.Fatalf("Test vector not appropriate") + } + + // Try and decode it. + hrp, data, err := DecodeNoLimit(input) + if err != nil { + t.Fatalf("Expected decoding of large string to work. Got error: %v", err) + } + + // Verify data for correctness. + if hrp != "1" { + t.Fatalf("Unexpected hrp: %v", hrp) + } + decodedHex := fmt.Sprintf("%x", data) + expected := "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000" + if decodedHex != expected { + t.Fatalf("Unexpected decoded data: %s", decodedHex) + } +} + +// TestBech32Base256 ensures decoding and encoding various bech32, HRPs, and +// data produces the expected results when using EncodeFromBase256 and +// DecodeToBase256. It includes tests for proper handling of case +// manipulations. +func TestBech32Base256(t *testing.T) { + tests := []struct { + name string // test name + encoded string // bech32 string to decode + hrp string // expected human-readable part + data string // expected hex-encoded data + err error // expected error + }{{ + name: "all uppercase, no data", + encoded: "A12UEL5L", + hrp: "a", + data: "", + }, { + name: "long hrp with separator and excluded chars, no data", + encoded: "an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs", + hrp: "an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio", + data: "", + }, { + name: "6 char hrp with data with leading zero", + encoded: "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", + hrp: "abcdef", + data: "00443214c74254b635cf84653a56d7c675be77df", + }, { + name: "hrp same as separator and max length encoded string", + encoded: "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j", + hrp: "1", + data: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, { + name: "5 char hrp with data chosen to produce human-readable data part", + encoded: "split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", + hrp: "split", + data: "c5f38b70305f519bf66d85fb6cf03058f3dde463ecd7918f2dc743918f2d", + }, { + name: "same as previous but with checksum invalidated", + encoded: "split1checkupstagehandshakeupstreamerranterredcaperred2y9e2w", + err: ErrInvalidChecksum{"2y9e3w", "2y9e3wlc445v", "2y9e2w"}, + }, { + name: "hrp with invalid character (space)", + encoded: "s lit1checkupstagehandshakeupstreamerranterredcaperredp8hs2p", + err: ErrInvalidCharacter(' '), + }, { + name: "hrp with invalid character (DEL)", + encoded: "spl\x7ft1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", + err: ErrInvalidCharacter(127), + }, { + name: "data part with invalid character (o)", + encoded: "split1cheo2y9e2w", + err: ErrNonCharsetChar('o'), + }, { + name: "data part too short", + encoded: "split1a2y9w", + err: ErrInvalidSeparatorIndex(5), + }, { + name: "empty hrp", + encoded: "1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", + err: ErrInvalidSeparatorIndex(0), + }, { + name: "no separator", + encoded: "pzry9x0s0muk", + err: ErrInvalidSeparatorIndex(-1), + }, { + name: "too long by one char", + encoded: "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j", + err: ErrInvalidLength(91), + }, { + name: "invalid due to mixed case in hrp", + encoded: "aBcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", + err: ErrMixedCase{}, + }, { + name: "invalid due to mixed case in data part", + encoded: "abcdef1Qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", + err: ErrMixedCase{}, + }} + + for _, test := range tests { + // Ensure the decode either produces an error or not as expected. + str := test.encoded + gotHRP, gotData, err := DecodeToBase256(str) + if test.err != err { + t.Errorf("%q: unexpected decode error -- got %v, want %v", + test.name, err, test.err) + continue + } + if err != nil { + // End test case here if a decoding error was expected. + continue + } + + // Ensure the expected HRP and original data are as expected. + if gotHRP != test.hrp { + t.Errorf("%q: mismatched decoded HRP -- got %q, want %q", test.name, + gotHRP, test.hrp) + continue + } + data, err := hex.DecodeString(test.data) + if err != nil { + t.Errorf("%q: invalid hex %q: %v", test.name, test.data, err) + continue + } + if !bytes.Equal(gotData, data) { + t.Errorf("%q: mismatched data -- got %x, want %x", test.name, + gotData, data) + continue + } + + // Encode the same data with the HRP converted to all uppercase and + // ensure the result is the lowercase version of the original encoded + // bech32 string. + gotEncoded, err := EncodeFromBase256(strings.ToUpper(test.hrp), data) + if err != nil { + t.Errorf("%q: unexpected uppercase HRP encode error: %v", test.name, + err) + } + wantEncoded := strings.ToLower(str) + if gotEncoded != wantEncoded { + t.Errorf("%q: mismatched encoding -- got %q, want %q", test.name, + gotEncoded, wantEncoded) + } + + // Encode the same data with the HRP converted to all lowercase and + // ensure the result is the lowercase version of the original encoded + // bech32 string. + gotEncoded, err = EncodeFromBase256(strings.ToLower(test.hrp), data) + if err != nil { + t.Errorf("%q: unexpected lowercase HRP encode error: %v", test.name, + err) + } + if gotEncoded != wantEncoded { + t.Errorf("%q: mismatched encoding -- got %q, want %q", test.name, + gotEncoded, wantEncoded) + } + + // Encode the same data with the HRP converted to mixed upper and + // lowercase and ensure the result is the lowercase version of the + // original encoded bech32 string. + var mixedHRPBuilder strings.Builder + for i, r := range test.hrp { + if i%2 == 0 { + mixedHRPBuilder.WriteString(strings.ToUpper(string(r))) + continue + } + mixedHRPBuilder.WriteRune(r) + } + gotEncoded, err = EncodeFromBase256(mixedHRPBuilder.String(), data) + if err != nil { + t.Errorf("%q: unexpected lowercase HRP encode error: %v", test.name, + err) + } + if gotEncoded != wantEncoded { + t.Errorf("%q: mismatched encoding -- got %q, want %q", test.name, + gotEncoded, wantEncoded) + } + + // Ensure a bit flip in the string is caught. + pos := strings.LastIndexAny(test.encoded, "1") + flipped := str[:pos+1] + string((str[pos+1] ^ 1)) + str[pos+2:] + _, _, err = DecodeToBase256(flipped) + if err == nil { + t.Error("expected decoding to fail") + } + } +} + +// BenchmarkEncodeDecodeCycle performs a benchmark for a full encode/decode +// cycle of a bech32 string. It also reports the allocation count, which we +// expect to be 2 for a fully optimized cycle. +func BenchmarkEncodeDecodeCycle(b *testing.B) { + // Use a fixed, 49-byte raw data for testing. + inputData, err := hex.DecodeString("cbe6365ddbcda9a9915422c3f091c13f8c7b2f263b8d34067bd12c274408473fa764871c9dd51b1bb34873b3473b633ed1") + if err != nil { + b.Fatalf("failed to initialize input data: %v", err) + } + + // Convert this into a 79-byte, base 32 byte slice. + base32Input, err := ConvertBits(inputData, 8, 5, true) + if err != nil { + b.Fatalf("failed to convert input to 32 bits-per-element: %v", err) + } + + // Use a fixed hrp for the tests. This should generate an encoded bech32 + // string of size 90 (the maximum allowed by BIP-173). + hrp := "bc" + + // Begin the benchmark. Given that we test one roundtrip per iteration + // (that is, one Encode() and one Decode() operation), we expect at most + // 2 allocations per reported test op. + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + str, err := Encode(hrp, base32Input) + if err != nil { + b.Fatalf("failed to encode input: %v", err) + } + + _, _, err = Decode(str) + if err != nil { + b.Fatalf("failed to decode string: %v", err) + } + } +} + +// TestConvertBits tests whether base conversion works using TestConvertBits(). +func TestConvertBits(t *testing.T) { + tests := []struct { + input string + output string + fromBits uint8 + toBits uint8 + pad bool + }{ + // Trivial empty conversions. + {"", "", 8, 5, false}, + {"", "", 8, 5, true}, + {"", "", 5, 8, false}, + {"", "", 5, 8, true}, + + // Conversions of 0 value with/without padding. + {"00", "00", 8, 5, false}, + {"00", "0000", 8, 5, true}, + {"0000", "00", 5, 8, false}, + {"0000", "0000", 5, 8, true}, + + // Testing when conversion ends exactly at the byte edge. This makes + // both padded and unpadded versions the same. + {"0000000000", "0000000000000000", 8, 5, false}, + {"0000000000", "0000000000000000", 8, 5, true}, + {"0000000000000000", "0000000000", 5, 8, false}, + {"0000000000000000", "0000000000", 5, 8, true}, + + // Conversions of full byte sequences. + {"ffffff", "1f1f1f1f1e", 8, 5, true}, + {"1f1f1f1f1e", "ffffff", 5, 8, false}, + {"1f1f1f1f1e", "ffffff00", 5, 8, true}, + + // Sample random conversions. + {"c9ca", "190705", 8, 5, false}, + {"c9ca", "19070500", 8, 5, true}, + {"19070500", "c9ca", 5, 8, false}, + {"19070500", "c9ca00", 5, 8, true}, + + // Test cases tested on TestConvertBitsFailures with their corresponding + // fixes. + {"ff", "1f1c", 8, 5, true}, + {"1f1c10", "ff20", 5, 8, true}, + + // Large conversions. + { + "cbe6365ddbcda9a9915422c3f091c13f8c7b2f263b8d34067bd12c274408473fa764871c9dd51b1bb34873b3473b633ed1", + "190f13030c170e1b1916141a13040a14040b011f01040e01071e0607160b1906070e06130801131b1a0416020e110008081c1f1a0e19040703120e1d0a06181b160d0407070c1a07070d11131d1408", + 8, 5, true, + }, + { + "190f13030c170e1b1916141a13040a14040b011f01040e01071e0607160b1906070e06130801131b1a0416020e110008081c1f1a0e19040703120e1d0a06181b160d0407070c1a07070d11131d1408", + "cbe6365ddbcda9a9915422c3f091c13f8c7b2f263b8d34067bd12c274408473fa764871c9dd51b1bb34873b3473b633ed100", + 5, 8, true, + }, + } + + for i, tc := range tests { + input, err := hex.DecodeString(tc.input) + if err != nil { + t.Fatalf("invalid test input data: %v", err) + } + + expected, err := hex.DecodeString(tc.output) + if err != nil { + t.Fatalf("invalid test output data: %v", err) + } + + actual, err := ConvertBits(input, tc.fromBits, tc.toBits, tc.pad) + if err != nil { + t.Fatalf("test case %d failed: %v", i, err) + } + + if !bytes.Equal(actual, expected) { + t.Fatalf("test case %d has wrong output; expected=%x actual=%x", + i, expected, actual) + } + } +} + +// TestConvertBitsFailures tests for the expected conversion failures of +// ConvertBits(). +func TestConvertBitsFailures(t *testing.T) { + tests := []struct { + input string + fromBits uint8 + toBits uint8 + pad bool + err error + }{ + // Not enough output bytes when not using padding. + {"ff", 8, 5, false, ErrInvalidIncompleteGroup{}}, + {"1f1c10", 5, 8, false, ErrInvalidIncompleteGroup{}}, + + // Unsupported bit conversions. + {"", 0, 5, false, ErrInvalidBitGroups{}}, + {"", 10, 5, false, ErrInvalidBitGroups{}}, + {"", 5, 0, false, ErrInvalidBitGroups{}}, + {"", 5, 10, false, ErrInvalidBitGroups{}}, + } + + for i, tc := range tests { + input, err := hex.DecodeString(tc.input) + if err != nil { + t.Fatalf("invalid test input data: %v", err) + } + + _, err = ConvertBits(input, tc.fromBits, tc.toBits, tc.pad) + if err != tc.err { + t.Fatalf("test case %d failure: expected '%v' got '%v'", i, + tc.err, err) + } + } + +} + +// BenchmarkConvertBitsDown benchmarks the speed and memory allocation behavior +// of ConvertBits when converting from a higher base into a lower base (e.g. 8 +// => 5). +// +// Only a single allocation is expected, which is used for the output array. +func BenchmarkConvertBitsDown(b *testing.B) { + // Use a fixed, 49-byte raw data for testing. + inputData, err := hex.DecodeString("cbe6365ddbcda9a9915422c3f091c13f8c7b2f263b8d34067bd12c274408473fa764871c9dd51b1bb34873b3473b633ed1") + if err != nil { + b.Fatalf("failed to initialize input data: %v", err) + } + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := ConvertBits(inputData, 8, 5, true) + if err != nil { + b.Fatalf("error converting bits: %v", err) + } + } +} + +// BenchmarkConvertBitsUp benchmarks the speed and memory allocation behavior +// of ConvertBits when converting from a lower base into a higher base (e.g. 5 +// => 8). +// +// Only a single allocation is expected, which is used for the output array. +func BenchmarkConvertBitsUp(b *testing.B) { + // Use a fixed, 79-byte raw data for testing. + inputData, err := hex.DecodeString("190f13030c170e1b1916141a13040a14040b011f01040e01071e0607160b1906070e06130801131b1a0416020e110008081c1f1a0e19040703120e1d0a06181b160d0407070c1a07070d11131d1408") + if err != nil { + b.Fatalf("failed to initialize input data: %v", err) + } + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := ConvertBits(inputData, 8, 5, true) + if err != nil { + b.Fatalf("error converting bits: %v", err) + } + } +} diff --git a/gnovm/stdlibs/crypto/bech32/error.gno b/gnovm/stdlibs/crypto/bech32/error.gno new file mode 100644 index 00000000000..dafe9b68516 --- /dev/null +++ b/gnovm/stdlibs/crypto/bech32/error.gno @@ -0,0 +1,83 @@ +// Copyright (c) 2019 The Decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package bech32 + +// ErrMixedCase is returned when the bech32 string has both lower and uppercase +// characters. +type ErrMixedCase struct{} + +func (e ErrMixedCase) Error() string { + return "string not all lowercase or all uppercase" +} + +// ErrInvalidBitGroups is returned when conversion is attempted between byte +// slices using bit-per-element of unsupported value. +type ErrInvalidBitGroups struct{} + +func (e ErrInvalidBitGroups) Error() string { + return "only bit groups between 1 and 8 allowed" +} + +// ErrInvalidIncompleteGroup is returned when then byte slice used as input has +// data of wrong length. +type ErrInvalidIncompleteGroup struct{} + +func (e ErrInvalidIncompleteGroup) Error() string { + return "invalid incomplete group" +} + +// ErrInvalidLength is returned when the bech32 string has an invalid length +// given the BIP-173 defined restrictions. +type ErrInvalidLength int + +func (e ErrInvalidLength) Error() string { + return "invalid bech32 string length" +} + +// ErrInvalidCharacter is returned when the bech32 string has a character +// outside the range of the supported charset. +type ErrInvalidCharacter rune + +func (e ErrInvalidCharacter) Error() string { + return "invalid character in string: " + string(e) +} + +// ErrInvalidSeparatorIndex is returned when the separator character '1' is +// in an invalid position in the bech32 string. +type ErrInvalidSeparatorIndex int + +func (e ErrInvalidSeparatorIndex) Error() string { + return "invalid separator index" + string(e) +} + +// ErrNonCharsetChar is returned when a character outside of the specific +// bech32 charset is used in the string. +type ErrNonCharsetChar rune + +func (e ErrNonCharsetChar) Error() string { + return "invalid character not part of charset" +} + +// ErrInvalidChecksum is returned when the extracted checksum of the string +// is different than what was expected. Both the original version, as well as +// the new bech32m checksum may be specified. +type ErrInvalidChecksum struct { + Expected string + ExpectedM string + Actual string +} + +func (e ErrInvalidChecksum) Error() string { + return "invalid checksum (expected (bech32=" + e.Expected + + " bech32m=)" + e.ExpectedM + ", got " + e.Actual + ")" +} + +// ErrInvalidDataByte is returned when a byte outside the range required for +// conversion into a string was found. +type ErrInvalidDataByte byte + +func (e ErrInvalidDataByte) Error() string { + return "invalid data byte: " + string(e) +} diff --git a/gnovm/stdlibs/crypto/bech32/version.gno b/gnovm/stdlibs/crypto/bech32/version.gno new file mode 100644 index 00000000000..147037db9aa --- /dev/null +++ b/gnovm/stdlibs/crypto/bech32/version.gno @@ -0,0 +1,43 @@ +package bech32 + +// ChecksumConst is a type that represents the currently defined bech32 +// checksum constants. +type ChecksumConst int + +const ( + // Version0Const is the original constant used in the checksum + // verification for bech32. + Version0Const ChecksumConst = 1 + + // VersionMConst is the new constant used for bech32m checksum + // verification. + VersionMConst ChecksumConst = 0x2bc830a3 +) + +// Version defines the current set of bech32 versions. +type Version uint8 + +const ( + // Version0 defines the original bech version. + Version0 Version = iota + + // VersionM is the new bech32 version defined in BIP-350, also known as + // bech32m. + VersionM + + // VersionUnknown denotes an unknown bech version. + VersionUnknown +) + +// VersionToConsts maps bech32 versions to the checksum constant to be used +// when encoding, and asserting a particular version when decoding. +var VersionToConsts = map[Version]ChecksumConst{ + Version0: Version0Const, + VersionM: VersionMConst, +} + +// ConstsToVersion maps a bech32 constant to the version it's associated with. +var ConstsToVersion = map[ChecksumConst]Version{ + Version0Const: Version0, + VersionMConst: VersionM, +} diff --git a/gnovm/stdlibs/generated.go b/gnovm/stdlibs/generated.go index d5ab052028f..6e757561ef2 100644 --- a/gnovm/stdlibs/generated.go +++ b/gnovm/stdlibs/generated.go @@ -640,106 +640,6 @@ var nativeFuncs = [...]NativeFunc{ )) }, }, - { - "std", - "derivePkgAddr", - []gno.FieldTypeExpr{ - {Name: gno.N("p0"), Type: gno.X("string")}, - }, - []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("string")}, - }, - false, - func(m *gno.Machine) { - b := m.LastBlock() - var ( - p0 string - rp0 = reflect.ValueOf(&p0).Elem() - ) - - gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - - r0 := libs_std.X_derivePkgAddr(p0) - - m.PushValue(gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(&r0).Elem(), - )) - }, - }, - { - "std", - "encodeBech32", - []gno.FieldTypeExpr{ - {Name: gno.N("p0"), Type: gno.X("string")}, - {Name: gno.N("p1"), Type: gno.X("[20]byte")}, - }, - []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("string")}, - }, - false, - func(m *gno.Machine) { - b := m.LastBlock() - var ( - p0 string - rp0 = reflect.ValueOf(&p0).Elem() - p1 [20]byte - rp1 = reflect.ValueOf(&p1).Elem() - ) - - gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) - - r0 := libs_std.X_encodeBech32(p0, p1) - - m.PushValue(gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(&r0).Elem(), - )) - }, - }, - { - "std", - "decodeBech32", - []gno.FieldTypeExpr{ - {Name: gno.N("p0"), Type: gno.X("string")}, - }, - []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("string")}, - {Name: gno.N("r1"), Type: gno.X("[20]byte")}, - {Name: gno.N("r2"), Type: gno.X("bool")}, - }, - false, - func(m *gno.Machine) { - b := m.LastBlock() - var ( - p0 string - rp0 = reflect.ValueOf(&p0).Elem() - ) - - gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - - r0, r1, r2 := libs_std.X_decodeBech32(p0) - - m.PushValue(gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(&r0).Elem(), - )) - m.PushValue(gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(&r1).Elem(), - )) - m.PushValue(gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(&r2).Elem(), - )) - }, - }, { "std", "assertCallerIsRealm", @@ -977,6 +877,7 @@ var initOrder = [...]string{ "bytes", "strings", "bufio", + "crypto/bech32", "encoding/binary", "math/bits", "math", diff --git a/gnovm/stdlibs/std/crypto.gno b/gnovm/stdlibs/std/crypto.gno index 402a6af3e22..adb6f687b56 100644 --- a/gnovm/stdlibs/std/crypto.gno +++ b/gnovm/stdlibs/std/crypto.gno @@ -1,7 +1,16 @@ package std +import ( + "crypto/bech32" + "crypto/sha256" + "errors" +) + type Address string // NOTE: bech32 +// bech32AddrPrefix defines the Bech32 prefix of an address +const bech32AddrPrefix = "g" + func (a Address) String() string { return string(a) } @@ -15,3 +24,61 @@ func (a Address) IsValid() bool { const RawAddressSize = 20 type RawAddress [RawAddressSize]byte + +func EncodeBech32(prefix string, bz [20]byte) Address { + b32, err := convertAndEncode(prefix, bz[:]) + if err != nil { + panic(err) // should not happen + } + return Address(b32) +} + +func DecodeBech32(addr Address) (string, [20]byte, bool) { + prefix, bz, err := decodeAndConvert(string(addr)) + if err != nil || len(bz) != 20 { + return "", [20]byte{}, false + } + return prefix, convertTo20Byte(bz), true +} + +func convertAndEncode(hrp string, data []byte) (string, error) { + converted, err := bech32.ConvertBits(data, 8, 5, true) + if err != nil { + return "", errors.New("encoding bech32 failed: " + err.Error()) + } + return bech32.Encode(hrp, converted) +} + +func decodeAndConvert(bech string) (string, []byte, error) { + hrp, data, err := bech32.DecodeNoLimit(bech) + if err != nil { + return "", nil, errors.New("decoding bech32 failed" + err.Error()) + } + converted, err := bech32.ConvertBits(data, 5, 8, false) + if err != nil { + return "", nil, errors.New("decoding bech32 failed" + err.Error()) + } + return hrp, converted, nil +} + +func DerivePkgAddr(pkgPath string) Address { + data := sumTruncated([]byte("pkgPath:" + pkgPath)) + return EncodeBech32(bech32AddrPrefix, data) +} + +func sumTruncated(bz []byte) [20]byte { + hash := sha256.Sum256(bz) + return convertTo20Byte(hash[:RawAddressSize]) +} + +func convertTo20Byte(in []byte) [20]byte { + /* For some reason [20]byte(bz) fails with: 'cannot convert []uint8 to ArrayKind' + Maybe there is an issue to create + */ + result := [20]byte{} + for index, b := range in { + result[index] = b + } + + return result +} diff --git a/gnovm/stdlibs/std/crypto_test.gno b/gnovm/stdlibs/std/crypto_test.gno index 75283c03523..70b42e43860 100644 --- a/gnovm/stdlibs/std/crypto_test.gno +++ b/gnovm/stdlibs/std/crypto_test.gno @@ -39,3 +39,22 @@ func TestValid(t *testing.T) { } } } + +func TestDerivePkgAddr(t *testing.T) { + type test struct { + inputPath string + expected string + } + + testCases := []test{ + {inputPath: "gno.land/r/gnoland/faucet", expected: "g1ttrq7mp4zy6dssnmgyyktnn4hcj3ys8xhju0n7"}, + {inputPath: "gno.land/r/demo/tamagotchi", expected: "g1a3tu874agjlkrpzt9x90xv3uzncapcn959yte4"}, + } + + for _, tc := range testCases { + result := DerivePkgAddr(tc.inputPath) + if result.String() != tc.expected { + t.Fatalf("Expected: %t, got: %t", tc.expected, result) + } + } +} diff --git a/gnovm/stdlibs/std/native.gno b/gnovm/stdlibs/std/native.gno index 0dcde1148e1..9cf8808a07e 100644 --- a/gnovm/stdlibs/std/native.gno +++ b/gnovm/stdlibs/std/native.gno @@ -45,25 +45,10 @@ func GetCallerAt(n int) Address { return Address(callerAt(n)) } -func DerivePkgAddr(pkgPath string) Address { - return Address(derivePkgAddr(pkgPath)) -} - -func EncodeBech32(prefix string, bz [20]byte) Address { - return Address(encodeBech32(prefix, bz)) -} - -func DecodeBech32(addr Address) (prefix string, bz [20]byte, ok bool) { - return decodeBech32(string(addr)) -} - // Variations which don't use named types. func origSend() (denoms []string, amounts []int64) func origCaller() string func origPkgAddr() string func callerAt(n int) string func getRealm(height int) (address string, pkgPath string) -func derivePkgAddr(pkgPath string) string -func encodeBech32(prefix string, bz [20]byte) string -func decodeBech32(addr string) (prefix string, bz [20]byte, ok bool) func assertCallerIsRealm() diff --git a/gnovm/stdlibs/std/native.go b/gnovm/stdlibs/std/native.go index fb181d9be31..9e398e907a2 100644 --- a/gnovm/stdlibs/std/native.go +++ b/gnovm/stdlibs/std/native.go @@ -2,7 +2,6 @@ package std import ( gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/tm2/pkg/bech32" "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/std" ) @@ -142,26 +141,6 @@ func currentRealm(m *gno.Machine) (address, pkgPath string) { return X_getRealm(m, 0) } -func X_derivePkgAddr(pkgPath string) string { - return string(gno.DerivePkgAddr(pkgPath).Bech32()) -} - -func X_encodeBech32(prefix string, bytes [20]byte) string { - b32, err := bech32.ConvertAndEncode(prefix, bytes[:]) - if err != nil { - panic(err) // should not happen - } - return b32 -} - -func X_decodeBech32(addr string) (prefix string, bytes [20]byte, ok bool) { - prefix, bz, err := bech32.Decode(addr) - if err != nil || len(bz) != 20 { - return "", [20]byte{}, false - } - return prefix, [20]byte(bz), true -} - func X_assertCallerIsRealm(m *gno.Machine) { frame := m.Frames[m.NumFrames()-2] if path := frame.LastPackage.PkgPath; !gno.IsRealmPath(path) { diff --git a/gnovm/tests/files/std5.gno b/gnovm/tests/files/std5.gno index e339d7a6364..2f9e98bb4ec 100644 --- a/gnovm/tests/files/std5.gno +++ b/gnovm/tests/files/std5.gno @@ -13,7 +13,7 @@ func main() { // Stacktrace: // panic: frame not found -// callerAt(n) +// callerAt(n) // gonative:std.callerAt // std.GetCallerAt(2) // std/native.gno:45 diff --git a/gnovm/tests/files/std8.gno b/gnovm/tests/files/std8.gno index ee717bf16be..dfc2b8ca5fd 100644 --- a/gnovm/tests/files/std8.gno +++ b/gnovm/tests/files/std8.gno @@ -23,7 +23,7 @@ func main() { // Stacktrace: // panic: frame not found -// callerAt(n) +// callerAt(n) // gonative:std.callerAt // std.GetCallerAt(4) // std/native.gno:45 diff --git a/gnovm/tests/files/zrealm_natbind0.gno b/gnovm/tests/files/zrealm_natbind0.gno index 8e5f641e734..e6ebef6252e 100644 --- a/gnovm/tests/files/zrealm_natbind0.gno +++ b/gnovm/tests/files/zrealm_natbind0.gno @@ -69,7 +69,7 @@ func main() { // "Closure": { // "@type": "/gno.RefValue", // "Escaped": true, -// "ObjectID": "a7f5397443359ea76c50be82c77f1f893a060925:9" +// "ObjectID": "a7f5397443359ea76c50be82c77f1f893a060925:10" // }, // "FileName": "native.gno", // "IsMethod": false, From d54d00470dd1be891e9c22896aa3841fcfe02eb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Tue, 14 Jan 2025 15:16:14 +0100 Subject: [PATCH 06/20] feat: add `EstimateGas` to gnoclient (#3498) ## Description Closes #1826 along with https://github.com/gnolang/tm2-js-client/pull/192 This PR introduces a method `EstimateGas` to estimate the gas used by a transaction in gnoclient. We can use this call to get a ballpark estimate if a transaction succeeds, and if it does, how much gas it actually used. --- gno.land/pkg/gnoclient/client_test.go | 157 ++++++++++++++++++++++++++ gno.land/pkg/gnoclient/client_txs.go | 47 +++++++- 2 files changed, 203 insertions(+), 1 deletion(-) diff --git a/gno.land/pkg/gnoclient/client_test.go b/gno.land/pkg/gnoclient/client_test.go index 1f8563d34fe..54a15420a66 100644 --- a/gno.land/pkg/gnoclient/client_test.go +++ b/gno.land/pkg/gnoclient/client_test.go @@ -1,8 +1,11 @@ package gnoclient import ( + "errors" "testing" + "github.com/gnolang/gno/tm2/pkg/amino" + abciErrors "github.com/gnolang/gno/tm2/pkg/bft/abci/example/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -1409,3 +1412,157 @@ func addPackageSigningSeparately(t *testing.T, client Client, cfg BaseTxCfg, msg require.NotNil(t, res) return res, nil } + +func TestClient_EstimateGas(t *testing.T) { + t.Parallel() + + t.Run("RPC client not set", func(t *testing.T) { + t.Parallel() + + c := &Client{ + RPCClient: nil, // not set + } + + estimate, err := c.EstimateGas(&std.Tx{}) + + assert.Zero(t, estimate) + assert.ErrorIs(t, err, ErrMissingRPCClient) + }) + + t.Run("unsuccessful query, rpc error", func(t *testing.T) { + t.Parallel() + + var ( + rpcErr = errors.New("rpc error") + mockRPCClient = &mockRPCClient{ + abciQuery: func(path string, data []byte) (*ctypes.ResultABCIQuery, error) { + require.Equal(t, simulatePath, path) + + var tx std.Tx + + require.NoError(t, amino.Unmarshal(data, &tx)) + + return nil, rpcErr + }, + } + ) + + c := &Client{ + RPCClient: mockRPCClient, + } + + estimate, err := c.EstimateGas(&std.Tx{}) + + assert.Zero(t, estimate) + assert.ErrorIs(t, err, rpcErr) + }) + + t.Run("unsuccessful query, process error", func(t *testing.T) { + t.Parallel() + + var ( + response = &ctypes.ResultABCIQuery{ + Response: abci.ResponseQuery{ + ResponseBase: abci.ResponseBase{ + Error: abciErrors.UnknownError{}, + }, + }, + } + mockRPCClient = &mockRPCClient{ + abciQuery: func(path string, data []byte) (*ctypes.ResultABCIQuery, error) { + require.Equal(t, simulatePath, path) + + var tx std.Tx + + require.NoError(t, amino.Unmarshal(data, &tx)) + + return response, nil + }, + } + ) + + c := &Client{ + RPCClient: mockRPCClient, + } + + estimate, err := c.EstimateGas(&std.Tx{}) + + assert.Zero(t, estimate) + assert.ErrorIs(t, err, abciErrors.UnknownError{}) + }) + + t.Run("invalid response format", func(t *testing.T) { + t.Parallel() + + var ( + response = &ctypes.ResultABCIQuery{ + Response: abci.ResponseQuery{ + Value: []byte("totally valid amino"), + }, + } + mockRPCClient = &mockRPCClient{ + abciQuery: func(path string, data []byte) (*ctypes.ResultABCIQuery, error) { + require.Equal(t, simulatePath, path) + + var tx std.Tx + + require.NoError(t, amino.Unmarshal(data, &tx)) + + return response, nil + }, + } + ) + + c := &Client{ + RPCClient: mockRPCClient, + } + + estimate, err := c.EstimateGas(&std.Tx{}) + + assert.Zero(t, estimate) + assert.ErrorContains(t, err, "unable to unmarshal gas estimation response") + }) + + t.Run("valid gas estimation", func(t *testing.T) { + t.Parallel() + + var ( + gasUsed = int64(100000) + deliverResp = &abci.ResponseDeliverTx{ + GasUsed: gasUsed, + } + ) + + // Encode the response + encodedResp, err := amino.Marshal(deliverResp) + require.NoError(t, err) + + var ( + response = &ctypes.ResultABCIQuery{ + Response: abci.ResponseQuery{ + Value: encodedResp, // valid amino binary + }, + } + mockRPCClient = &mockRPCClient{ + abciQuery: func(path string, data []byte) (*ctypes.ResultABCIQuery, error) { + require.Equal(t, simulatePath, path) + + var tx std.Tx + + require.NoError(t, amino.Unmarshal(data, &tx)) + + return response, nil + }, + } + ) + + c := &Client{ + RPCClient: mockRPCClient, + } + + estimate, err := c.EstimateGas(&std.Tx{}) + + require.NoError(t, err) + assert.Equal(t, gasUsed, estimate) + }) +} diff --git a/gno.land/pkg/gnoclient/client_txs.go b/gno.land/pkg/gnoclient/client_txs.go index d7f6f053242..ab520eceda1 100644 --- a/gno.land/pkg/gnoclient/client_txs.go +++ b/gno.land/pkg/gnoclient/client_txs.go @@ -1,8 +1,11 @@ package gnoclient import ( + "fmt" + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" "github.com/gnolang/gno/tm2/pkg/amino" + 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/errors" "github.com/gnolang/gno/tm2/pkg/sdk/bank" @@ -16,6 +19,8 @@ var ( ErrMissingRPCClient = errors.New("missing RPCClient") ) +const simulatePath = ".app/simulate" + // BaseTxCfg defines the base transaction configuration, shared by all message types type BaseTxCfg struct { GasFee string // Gas fee @@ -292,4 +297,44 @@ func (c *Client) BroadcastTxCommit(signedTx *std.Tx) (*ctypes.ResultBroadcastTxC return bres, nil } -// TODO: Add more functionality, examples, and unit tests. +// EstimateGas returns the least amount of gas required +// for the transaction to go through on the chain (minimum gas wanted). +// The estimation process assumes the transaction is properly signed +func (c *Client) EstimateGas(tx *std.Tx) (int64, error) { + // Make sure the RPC client is set + if err := c.validateRPCClient(); err != nil { + return 0, err + } + + // Prepare the transaction. + // The transaction needs to be amino-binary encoded + // in order to be estimated + encodedTx, err := amino.Marshal(tx) + if err != nil { + return 0, fmt.Errorf("unable to marshal tx: %w", err) + } + + // Perform the simulation query + resp, err := c.RPCClient.ABCIQuery(simulatePath, encodedTx) + if err != nil { + return 0, fmt.Errorf("unable to perform ABCI query: %w", err) + } + + // Extract the query response + if err = resp.Response.Error; err != nil { + return 0, fmt.Errorf("error encountered during ABCI query: %w", err) + } + + var deliverTx abci.ResponseDeliverTx + if err = amino.Unmarshal(resp.Response.Value, &deliverTx); err != nil { + return 0, fmt.Errorf("unable to unmarshal gas estimation response: %w", err) + } + + if err = deliverTx.Error; err != nil { + return 0, fmt.Errorf("error encountered during gas estimation: %w", err) + } + + // Return the actual value returned by the node + // for executing the transaction + return deliverTx.GasUsed, nil +} From 7761c3ba29695b1621c618d8382d696433622761 Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Tue, 14 Jan 2025 17:20:59 +0100 Subject: [PATCH 07/20] chore: revert "feat(gnovm): implement overflow checking at VM level" (#3508) Revert #3250 This reverts commit 68aff6464dfba782903cdb5e3b318a9b233a479e. This PR was merged before discussions were complete. It was finally rejected because it does not comply with Go specification, which stipulates that overflow on signed integers do not trigger runtime panic. See https://go.dev/ref/spec#Integer_overflow --- examples/gno.land/p/demo/grc/grc20/token.gno | 22 +- gnovm/pkg/gnolang/op_bench_test.go | 70 --- gnovm/pkg/gnolang/op_binary.go | 305 +++++------ gnovm/pkg/gnolang/op_inc_dec.go | 25 +- gnovm/stdlibs/generated.go | 1 + gnovm/stdlibs/math/const_test.gno | 77 +-- gnovm/stdlibs/math/overflow/overflow.gno | 501 ++++++++++++++++++ gnovm/stdlibs/math/overflow/overflow_test.gno | 200 +++++++ gnovm/stdlibs/std/coins.gno | 26 +- gnovm/tests/files/overflow0.gno | 10 - gnovm/tests/files/overflow1.gno | 10 - gnovm/tests/files/overflow2.gno | 10 - gnovm/tests/files/overflow3.gno | 10 - gnovm/tests/files/overflow4.gno | 10 - gnovm/tests/files/overflow5.gno | 10 - gnovm/tests/files/recover14.gno | 2 +- misc/genstd/util.go | 3 +- 17 files changed, 874 insertions(+), 418 deletions(-) delete mode 100644 gnovm/pkg/gnolang/op_bench_test.go create mode 100644 gnovm/stdlibs/math/overflow/overflow.gno create mode 100644 gnovm/stdlibs/math/overflow/overflow_test.gno delete mode 100644 gnovm/tests/files/overflow0.gno delete mode 100644 gnovm/tests/files/overflow1.gno delete mode 100644 gnovm/tests/files/overflow2.gno delete mode 100644 gnovm/tests/files/overflow3.gno delete mode 100644 gnovm/tests/files/overflow4.gno delete mode 100644 gnovm/tests/files/overflow5.gno diff --git a/examples/gno.land/p/demo/grc/grc20/token.gno b/examples/gno.land/p/demo/grc/grc20/token.gno index 4986eaebf04..3ab3abc63a3 100644 --- a/examples/gno.land/p/demo/grc/grc20/token.gno +++ b/examples/gno.land/p/demo/grc/grc20/token.gno @@ -1,6 +1,7 @@ package grc20 import ( + "math/overflow" "std" "strconv" @@ -169,24 +170,17 @@ func (led *PrivateLedger) Approve(owner, spender std.Address, amount uint64) err } // Mint increases the total supply of the token and adds the specified amount to the specified address. -func (led *PrivateLedger) Mint(address std.Address, amount uint64) (err error) { +func (led *PrivateLedger) Mint(address std.Address, amount uint64) error { if !address.IsValid() { return ErrInvalidAddress } - defer func() { - if r := recover(); r != nil { - if r != "addition overflow" { - panic(r) - } - err = ErrOverflow - } - }() - - // Convert amount and totalSupply to signed integers to enable - // overflow checking (not occuring on unsigned) when computing the sum. - // The maximum value for totalSupply is therefore 1<<63. - sum := int64(led.totalSupply) + int64(amount) + // XXX: math/overflow is not supporting uint64. + // This checks prevents overflow but makes the totalSupply limited to a uint63. + sum, ok := overflow.Add64(int64(led.totalSupply), int64(amount)) + if !ok { + return ErrOverflow + } led.totalSupply = uint64(sum) currentBalance := led.balanceOf(address) diff --git a/gnovm/pkg/gnolang/op_bench_test.go b/gnovm/pkg/gnolang/op_bench_test.go deleted file mode 100644 index 5874f980285..00000000000 --- a/gnovm/pkg/gnolang/op_bench_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package gnolang - -import ( - "testing" - - "github.com/gnolang/gno/tm2/pkg/overflow" -) - -func BenchmarkOpAdd(b *testing.B) { - m := NewMachine("bench", nil) - x := TypedValue{T: IntType} - x.SetInt(4) - y := TypedValue{T: IntType} - y.SetInt(3) - - b.ResetTimer() - - for range b.N { - m.PushOp(OpHalt) - m.PushExpr(&BinaryExpr{}) - m.PushValue(x) - m.PushValue(y) - m.PushOp(OpAdd) - m.Run() - } -} - -//go:noinline -func AddNoOverflow(x, y int) int { return x + y } - -func BenchmarkAddNoOverflow(b *testing.B) { - x, y := 4, 3 - c := 0 - for range b.N { - c = AddNoOverflow(x, y) - } - if c != 7 { - b.Error("invalid result") - } -} - -func BenchmarkAddOverflow(b *testing.B) { - x, y := 4, 3 - c := 0 - for range b.N { - c = overflow.Addp(x, y) - } - if c != 7 { - b.Error("invalid result") - } -} - -func TestOpAdd1(t *testing.T) { - m := NewMachine("test", nil) - a := TypedValue{T: IntType} - a.SetInt(4) - b := TypedValue{T: IntType} - b.SetInt(3) - t.Log("a:", a, "b:", b) - - start := m.NumValues - m.PushOp(OpHalt) - m.PushExpr(&BinaryExpr{}) - m.PushValue(a) - m.PushValue(b) - m.PushOp(OpAdd) - m.Run() - res := m.ReapValues(start) - t.Log("res:", res) -} diff --git a/gnovm/pkg/gnolang/op_binary.go b/gnovm/pkg/gnolang/op_binary.go index 765f3ccbfbd..0e8eec9db23 100644 --- a/gnovm/pkg/gnolang/op_binary.go +++ b/gnovm/pkg/gnolang/op_binary.go @@ -7,7 +7,6 @@ import ( "github.com/cockroachdb/apd/v3" "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat" - "github.com/gnolang/gno/tm2/pkg/overflow" ) // ---------------------------------------- @@ -185,9 +184,7 @@ func (m *Machine) doOpAdd() { } // add rv to lv. - if err := addAssign(m.Alloc, lv, rv); err != nil { - panic(err) - } + addAssign(m.Alloc, lv, rv) } func (m *Machine) doOpSub() { @@ -201,9 +198,7 @@ func (m *Machine) doOpSub() { } // sub rv from lv. - if err := subAssign(lv, rv); err != nil { - panic(err) - } + subAssign(lv, rv) } func (m *Machine) doOpBor() { @@ -259,7 +254,8 @@ func (m *Machine) doOpQuo() { } // lv / rv - if err := quoAssign(lv, rv); err != nil { + err := quoAssign(lv, rv) + if err != nil { panic(err) } } @@ -275,7 +271,8 @@ func (m *Machine) doOpRem() { } // lv % rv - if err := remAssign(lv, rv); err != nil { + err := remAssign(lv, rv) + if err != nil { panic(err) } } @@ -687,38 +684,23 @@ func isGeq(lv, rv *TypedValue) bool { } } -// addAssign adds lv to rv and stores the result to lv. -// It returns an exception in case of overflow on signed integers. -// The assignement is performed even in case of exception. -func addAssign(alloc *Allocator, lv, rv *TypedValue) *Exception { +// for doOpAdd and doOpAddAssign. +func addAssign(alloc *Allocator, lv, rv *TypedValue) { // set the result in lv. // NOTE this block is replicated in op_assign.go - ok := true switch baseOf(lv.T) { case StringType, UntypedStringType: lv.V = alloc.NewString(lv.GetString() + rv.GetString()) - // Signed integers may overflow, which triggers an exception. case IntType: - var r int - r, ok = overflow.Add(lv.GetInt(), rv.GetInt()) - lv.SetInt(r) + lv.SetInt(lv.GetInt() + rv.GetInt()) case Int8Type: - var r int8 - r, ok = overflow.Add8(lv.GetInt8(), rv.GetInt8()) - lv.SetInt8(r) + lv.SetInt8(lv.GetInt8() + rv.GetInt8()) case Int16Type: - var r int16 - r, ok = overflow.Add16(lv.GetInt16(), rv.GetInt16()) - lv.SetInt16(r) + lv.SetInt16(lv.GetInt16() + rv.GetInt16()) case Int32Type, UntypedRuneType: - var r int32 - r, ok = overflow.Add32(lv.GetInt32(), rv.GetInt32()) - lv.SetInt32(r) + lv.SetInt32(lv.GetInt32() + rv.GetInt32()) case Int64Type: - var r int64 - r, ok = overflow.Add64(lv.GetInt64(), rv.GetInt64()) - lv.SetInt64(r) - // Unsigned integers do not overflow, they just wrap. + lv.SetInt64(lv.GetInt64() + rv.GetInt64()) case UintType: lv.SetUint(lv.GetUint() + rv.GetUint()) case Uint8Type: @@ -758,42 +740,23 @@ func addAssign(alloc *Allocator, lv, rv *TypedValue) *Exception { lv.T, )) } - if !ok { - return &Exception{Value: typedString("addition overflow")} - } - return nil } -// subAssign subtracts lv to rv and stores the result to lv. -// It returns an exception in case of overflow on signed integers. -// The subtraction is performed even in case of exception. -func subAssign(lv, rv *TypedValue) *Exception { +// for doOpSub and doOpSubAssign. +func subAssign(lv, rv *TypedValue) { // set the result in lv. // NOTE this block is replicated in op_assign.go - ok := true switch baseOf(lv.T) { - // Signed integers may overflow, which triggers an exception. case IntType: - var r int - r, ok = overflow.Sub(lv.GetInt(), rv.GetInt()) - lv.SetInt(r) + lv.SetInt(lv.GetInt() - rv.GetInt()) case Int8Type: - var r int8 - r, ok = overflow.Sub8(lv.GetInt8(), rv.GetInt8()) - lv.SetInt8(r) + lv.SetInt8(lv.GetInt8() - rv.GetInt8()) case Int16Type: - var r int16 - r, ok = overflow.Sub16(lv.GetInt16(), rv.GetInt16()) - lv.SetInt16(r) + lv.SetInt16(lv.GetInt16() - rv.GetInt16()) case Int32Type, UntypedRuneType: - var r int32 - r, ok = overflow.Sub32(lv.GetInt32(), rv.GetInt32()) - lv.SetInt32(r) + lv.SetInt32(lv.GetInt32() - rv.GetInt32()) case Int64Type: - var r int64 - r, ok = overflow.Sub64(lv.GetInt64(), rv.GetInt64()) - lv.SetInt64(r) - // Unsigned integers do not overflow, they just wrap. + lv.SetInt64(lv.GetInt64() - rv.GetInt64()) case UintType: lv.SetUint(lv.GetUint() - rv.GetUint()) case Uint8Type: @@ -833,39 +796,23 @@ func subAssign(lv, rv *TypedValue) *Exception { lv.T, )) } - if !ok { - return &Exception{Value: typedString("subtraction overflow")} - } - return nil } // for doOpMul and doOpMulAssign. -func mulAssign(lv, rv *TypedValue) *Exception { +func mulAssign(lv, rv *TypedValue) { // set the result in lv. // NOTE this block is replicated in op_assign.go - ok := true switch baseOf(lv.T) { - // Signed integers may overflow, which triggers a panic. case IntType: - var r int - r, ok = overflow.Mul(lv.GetInt(), rv.GetInt()) - lv.SetInt(r) + lv.SetInt(lv.GetInt() * rv.GetInt()) case Int8Type: - var r int8 - r, ok = overflow.Mul8(lv.GetInt8(), rv.GetInt8()) - lv.SetInt8(r) + lv.SetInt8(lv.GetInt8() * rv.GetInt8()) case Int16Type: - var r int16 - r, ok = overflow.Mul16(lv.GetInt16(), rv.GetInt16()) - lv.SetInt16(r) + lv.SetInt16(lv.GetInt16() * rv.GetInt16()) case Int32Type, UntypedRuneType: - var r int32 - r, ok = overflow.Mul32(lv.GetInt32(), rv.GetInt32()) - lv.SetInt32(r) + lv.SetInt32(lv.GetInt32() * rv.GetInt32()) case Int64Type: - var r int64 - r, ok = overflow.Mul64(lv.GetInt64(), rv.GetInt64()) - lv.SetInt64(r) + lv.SetInt64(lv.GetInt64() * rv.GetInt64()) case UintType: lv.SetUint(lv.GetUint() * rv.GetUint()) case Uint8Type: @@ -903,102 +850,95 @@ func mulAssign(lv, rv *TypedValue) *Exception { lv.T, )) } - if !ok { - return &Exception{Value: typedString("multiplication overflow")} - } - return nil } // for doOpQuo and doOpQuoAssign. func quoAssign(lv, rv *TypedValue) *Exception { + expt := &Exception{ + Value: typedString("division by zero"), + } + // set the result in lv. // NOTE this block is replicated in op_assign.go - ok := true switch baseOf(lv.T) { - // Signed integers may overflow or cause a division by 0, which triggers a panic. case IntType: - var q int - q, _, ok = overflow.Quotient(lv.GetInt(), rv.GetInt()) - lv.SetInt(q) + if rv.GetInt() == 0 { + return expt + } + lv.SetInt(lv.GetInt() / rv.GetInt()) case Int8Type: - var q int8 - q, _, ok = overflow.Quotient8(lv.GetInt8(), rv.GetInt8()) - lv.SetInt8(q) + if rv.GetInt8() == 0 { + return expt + } + lv.SetInt8(lv.GetInt8() / rv.GetInt8()) case Int16Type: - var q int16 - q, _, ok = overflow.Quotient16(lv.GetInt16(), rv.GetInt16()) - lv.SetInt16(q) + if rv.GetInt16() == 0 { + return expt + } + lv.SetInt16(lv.GetInt16() / rv.GetInt16()) case Int32Type, UntypedRuneType: - var q int32 - q, _, ok = overflow.Quotient32(lv.GetInt32(), rv.GetInt32()) - lv.SetInt32(q) + if rv.GetInt32() == 0 { + return expt + } + lv.SetInt32(lv.GetInt32() / rv.GetInt32()) case Int64Type: - var q int64 - q, _, ok = overflow.Quotient64(lv.GetInt64(), rv.GetInt64()) - lv.SetInt64(q) - // Unsigned integers do not cause overflow, but a division by 0 may still occur. + if rv.GetInt64() == 0 { + return expt + } + lv.SetInt64(lv.GetInt64() / rv.GetInt64()) case UintType: - y := rv.GetUint() - ok = y != 0 - if ok { - lv.SetUint(lv.GetUint() / y) + if rv.GetUint() == 0 { + return expt } + lv.SetUint(lv.GetUint() / rv.GetUint()) case Uint8Type: - y := rv.GetUint8() - ok = y != 0 - if ok { - lv.SetUint8(lv.GetUint8() / y) + if rv.GetUint8() == 0 { + return expt } + lv.SetUint8(lv.GetUint8() / rv.GetUint8()) case DataByteType: - y := rv.GetUint8() - ok = y != 0 - if ok { - lv.SetDataByte(lv.GetDataByte() / y) + if rv.GetUint8() == 0 { + return expt } + lv.SetDataByte(lv.GetDataByte() / rv.GetUint8()) case Uint16Type: - y := rv.GetUint16() - ok = y != 0 - if ok { - lv.SetUint16(lv.GetUint16() / y) + if rv.GetUint16() == 0 { + return expt } + lv.SetUint16(lv.GetUint16() / rv.GetUint16()) case Uint32Type: - y := rv.GetUint32() - ok = y != 0 - if ok { - lv.SetUint32(lv.GetUint32() / y) + if rv.GetUint32() == 0 { + return expt } + lv.SetUint32(lv.GetUint32() / rv.GetUint32()) case Uint64Type: - y := rv.GetUint64() - ok = y != 0 - if ok { - lv.SetUint64(lv.GetUint64() / y) + if rv.GetUint64() == 0 { + return expt } - // XXX Handling float overflows is more complex. + lv.SetUint64(lv.GetUint64() / rv.GetUint64()) case Float32Type: // NOTE: gno doesn't fuse *+. - ok = !softfloat.Feq32(rv.GetFloat32(), softfloat.Fintto32(0)) + ok := !softfloat.Feq32(rv.GetFloat32(), softfloat.Fintto32(0)) if ok { lv.SetFloat32(softfloat.Fdiv32(lv.GetFloat32(), rv.GetFloat32())) } case Float64Type: // NOTE: gno doesn't fuse *+. - ok = !softfloat.Feq64(rv.GetFloat64(), softfloat.Fintto64(0)) + ok := !softfloat.Feq64(rv.GetFloat64(), softfloat.Fintto64(0)) if ok { lv.SetFloat64(softfloat.Fdiv64(lv.GetFloat64(), rv.GetFloat64())) } case BigintType, UntypedBigintType: if rv.GetBigInt().Sign() == 0 { - ok = false - break + return expt } lb := lv.GetBigInt() lb = big.NewInt(0).Quo(lb, rv.GetBigInt()) lv.V = BigintValue{V: lb} case BigdecType, UntypedBigdecType: if rv.GetBigDec().Cmp(apd.New(0, 0)) == 0 { - ok = false - break + return expt } lb := lv.GetBigDec() rb := rv.GetBigDec() @@ -1015,83 +955,81 @@ func quoAssign(lv, rv *TypedValue) *Exception { )) } - if !ok { - return &Exception{Value: typedString("division by zero or overflow")} - } return nil } // for doOpRem and doOpRemAssign. func remAssign(lv, rv *TypedValue) *Exception { + expt := &Exception{ + Value: typedString("division by zero"), + } + // set the result in lv. // NOTE this block is replicated in op_assign.go - ok := true switch baseOf(lv.T) { - // Signed integers may overflow or cause a division by 0, which triggers a panic. case IntType: - var r int - _, r, ok = overflow.Quotient(lv.GetInt(), rv.GetInt()) - lv.SetInt(r) + if rv.GetInt() == 0 { + return expt + } + lv.SetInt(lv.GetInt() % rv.GetInt()) case Int8Type: - var r int8 - _, r, ok = overflow.Quotient8(lv.GetInt8(), rv.GetInt8()) - lv.SetInt8(r) + if rv.GetInt8() == 0 { + return expt + } + lv.SetInt8(lv.GetInt8() % rv.GetInt8()) case Int16Type: - var r int16 - _, r, ok = overflow.Quotient16(lv.GetInt16(), rv.GetInt16()) - lv.SetInt16(r) + if rv.GetInt16() == 0 { + return expt + } + lv.SetInt16(lv.GetInt16() % rv.GetInt16()) case Int32Type, UntypedRuneType: - var r int32 - _, r, ok = overflow.Quotient32(lv.GetInt32(), rv.GetInt32()) - lv.SetInt32(r) + if rv.GetInt32() == 0 { + return expt + } + lv.SetInt32(lv.GetInt32() % rv.GetInt32()) case Int64Type: - var r int64 - _, r, ok = overflow.Quotient64(lv.GetInt64(), rv.GetInt64()) - lv.SetInt64(r) - // Unsigned integers do not cause overflow, but a division by 0 may still occur. + if rv.GetInt64() == 0 { + return expt + } + lv.SetInt64(lv.GetInt64() % rv.GetInt64()) case UintType: - y := rv.GetUint() - ok = y != 0 - if ok { - lv.SetUint(lv.GetUint() % y) + if rv.GetUint() == 0 { + return expt } + lv.SetUint(lv.GetUint() % rv.GetUint()) case Uint8Type: - y := rv.GetUint8() - ok = y != 0 - if ok { - lv.SetUint8(lv.GetUint8() % y) + if rv.GetUint8() == 0 { + return expt } + lv.SetUint8(lv.GetUint8() % rv.GetUint8()) case DataByteType: - y := rv.GetUint8() - ok = y != 0 - if ok { - lv.SetDataByte(lv.GetDataByte() % y) + if rv.GetUint8() == 0 { + return expt } + lv.SetDataByte(lv.GetDataByte() % rv.GetUint8()) case Uint16Type: - y := rv.GetUint16() - ok = y != 0 - if ok { - lv.SetUint16(lv.GetUint16() % y) + if rv.GetUint16() == 0 { + return expt } + lv.SetUint16(lv.GetUint16() % rv.GetUint16()) case Uint32Type: - y := rv.GetUint32() - ok = y != 0 - if ok { - lv.SetUint32(lv.GetUint32() % y) + if rv.GetUint32() == 0 { + return expt } + lv.SetUint32(lv.GetUint32() % rv.GetUint32()) case Uint64Type: - y := rv.GetUint64() - ok = y != 0 - if ok { - lv.SetUint64(lv.GetUint64() % y) + if rv.GetUint64() == 0 { + return expt } + lv.SetUint64(lv.GetUint64() % rv.GetUint64()) case BigintType, UntypedBigintType: - ok = rv.GetBigInt().Sign() != 0 - if ok { - lb := lv.GetBigInt() - lb = big.NewInt(0).Rem(lb, rv.GetBigInt()) - lv.V = BigintValue{V: lb} + if rv.GetBigInt().Sign() == 0 { + return expt } + + lb := lv.GetBigInt() + lb = big.NewInt(0).Rem(lb, rv.GetBigInt()) + lv.V = BigintValue{V: lb} default: panic(fmt.Sprintf( "operators %% and %%= not defined for %s", @@ -1099,9 +1037,6 @@ func remAssign(lv, rv *TypedValue) *Exception { )) } - if !ok { - return &Exception{Value: typedString("division by zero or overflow")} - } return nil } diff --git a/gnovm/pkg/gnolang/op_inc_dec.go b/gnovm/pkg/gnolang/op_inc_dec.go index c67a4be6ed5..708aae821ac 100644 --- a/gnovm/pkg/gnolang/op_inc_dec.go +++ b/gnovm/pkg/gnolang/op_inc_dec.go @@ -6,7 +6,6 @@ import ( "github.com/cockroachdb/apd/v3" "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat" - "github.com/gnolang/gno/tm2/pkg/overflow" ) func (m *Machine) doOpInc() { @@ -33,18 +32,16 @@ func (m *Machine) doOpInc() { // because it could be a type alias // type num int switch baseOf(lv.T) { - // Signed integers may overflow, which triggers a panic. case IntType: - lv.SetInt(overflow.Addp(lv.GetInt(), 1)) + lv.SetInt(lv.GetInt() + 1) case Int8Type: - lv.SetInt8(overflow.Add8p(lv.GetInt8(), 1)) + lv.SetInt8(lv.GetInt8() + 1) case Int16Type: - lv.SetInt16(overflow.Add16p(lv.GetInt16(), 1)) + lv.SetInt16(lv.GetInt16() + 1) case Int32Type: - lv.SetInt32(overflow.Add32p(lv.GetInt32(), 1)) + lv.SetInt32(lv.GetInt32() + 1) case Int64Type: - lv.SetInt64(overflow.Add64p(lv.GetInt64(), 1)) - // Unsigned integers do not overflow, they just wrap. + lv.SetInt64(lv.GetInt64() + 1) case UintType: lv.SetUint(lv.GetUint() + 1) case Uint8Type: @@ -105,18 +102,16 @@ func (m *Machine) doOpDec() { } } switch baseOf(lv.T) { - // Signed integers may overflow, which triggers a panic. case IntType: - lv.SetInt(overflow.Subp(lv.GetInt(), 1)) + lv.SetInt(lv.GetInt() - 1) case Int8Type: - lv.SetInt8(overflow.Sub8p(lv.GetInt8(), 1)) + lv.SetInt8(lv.GetInt8() - 1) case Int16Type: - lv.SetInt16(overflow.Sub16p(lv.GetInt16(), 1)) + lv.SetInt16(lv.GetInt16() - 1) case Int32Type: - lv.SetInt32(overflow.Sub32p(lv.GetInt32(), 1)) + lv.SetInt32(lv.GetInt32() - 1) case Int64Type: - lv.SetInt64(overflow.Sub64p(lv.GetInt64(), 1)) - // Unsigned integers do not overflow, they just wrap. + lv.SetInt64(lv.GetInt64() - 1) case UintType: lv.SetUint(lv.GetUint() - 1) case Uint8Type: diff --git a/gnovm/stdlibs/generated.go b/gnovm/stdlibs/generated.go index 6e757561ef2..ab35fc6b6bf 100644 --- a/gnovm/stdlibs/generated.go +++ b/gnovm/stdlibs/generated.go @@ -896,6 +896,7 @@ var initOrder = [...]string{ "hash", "hash/adler32", "html", + "math/overflow", "math/rand", "path", "sort", diff --git a/gnovm/stdlibs/math/const_test.gno b/gnovm/stdlibs/math/const_test.gno index fbe59d61878..b892a12898b 100644 --- a/gnovm/stdlibs/math/const_test.gno +++ b/gnovm/stdlibs/math/const_test.gno @@ -31,76 +31,19 @@ func TestMaxUint(t *testing.T) { } func TestMaxInt(t *testing.T) { - defer func() { - if r := recover(); r != nil { - if r != "addition overflow" { - panic(r) - } - } - }() - v := int(math.MaxInt) - if v+1 == math.MinInt { - t.Errorf("int should overflow") + if v := int(math.MaxInt); v+1 != math.MinInt { + t.Errorf("MaxInt should wrap around to MinInt: %d", v+1) } - t.Errorf("expected panic did not occur") -} - -func TestMaxInt8(t *testing.T) { - defer func() { - if r := recover(); r != nil { - if r != "addition overflow" { - panic(r) - } - } - }() - v := int8(math.MaxInt8) - if v+1 == math.MinInt8 { - t.Errorf("int8 should overflow") + if v := int8(math.MaxInt8); v+1 != math.MinInt8 { + t.Errorf("MaxInt8 should wrap around to MinInt8: %d", v+1) } - t.Errorf("expected panic did not occur") -} - -func TestMaxInt16(t *testing.T) { - defer func() { - if r := recover(); r != nil { - if r != "addition overflow" { - panic(r) - } - } - }() - v := int16(math.MaxInt16) - if v+1 == math.MinInt16 { - t.Errorf("int16 should overflow") + if v := int16(math.MaxInt16); v+1 != math.MinInt16 { + t.Errorf("MaxInt16 should wrap around to MinInt16: %d", v+1) } - t.Errorf("expected panic did not occur") -} - -func TestMaxInt32(t *testing.T) { - defer func() { - if r := recover(); r != nil { - if r != "addition overflow" { - panic(r) - } - } - }() - v := int32(math.MaxInt32) - if v+1 == math.MinInt32 { - t.Errorf("int32 should overflow") + if v := int32(math.MaxInt32); v+1 != math.MinInt32 { + t.Errorf("MaxInt32 should wrap around to MinInt32: %d", v+1) } - t.Errorf("expected panic did not occur") -} - -func TestMaxInt64(t *testing.T) { - defer func() { - if r := recover(); r != nil { - if r != "addition overflow" { - panic(r) - } - } - }() - v := int64(math.MaxInt64) - if v+1 == math.MinInt64 { - t.Errorf("int64 should overflow") + if v := int64(math.MaxInt64); v+1 != math.MinInt64 { + t.Errorf("MaxInt64 should wrap around to MinInt64: %d", v+1) } - t.Errorf("expected panic did not occur") } diff --git a/gnovm/stdlibs/math/overflow/overflow.gno b/gnovm/stdlibs/math/overflow/overflow.gno new file mode 100644 index 00000000000..0bc2e03a522 --- /dev/null +++ b/gnovm/stdlibs/math/overflow/overflow.gno @@ -0,0 +1,501 @@ +// This is modified from https://github.com/JohnCGriffin/overflow (MIT). +// NOTE: there was a bug with the original Quotient* functions, and +// testing method. These have been fixed here, and tests ported to +// tests/files/maths_int*.go respectively. +// Note: moved over from p/demo/maths. + +/* +Package overflow offers overflow-checked integer arithmetic operations +for int, int32, and int64. Each of the operations returns a +result,bool combination. This was prompted by the need to know when +to flow into higher precision types from the math.big library. + +For instance, assuing a 64 bit machine: + +10 + 20 -> 30 +int(math.MaxInt64) + 1 -> -9223372036854775808 + +whereas + +overflow.Add(10,20) -> (30, true) +overflow.Add(math.MaxInt64,1) -> (0, false) + +Add, Sub, Mul, Div are for int. Add64, Add32, etc. are specifically sized. + +If anybody wishes an unsigned version, submit a pull request for code +and new tests. +*/ +package overflow + +import "math" + +//go:generate ./overflow_template.sh + +func _is64Bit() bool { + maxU32 := uint(math.MaxUint32) + return ((maxU32 << 1) >> 1) == maxU32 +} + +/********** PARTIAL TEST COVERAGE FROM HERE DOWN ************* + +The only way that I could see to do this is a combination of +my normal 64 bit system and a GopherJS running on Node. My +understanding is that its ints are 32 bit. + +So, FEEL FREE to carefully review the code visually. + +*************************************************************/ + +// Unspecified size, i.e. normal signed int + +// Add sums two ints, returning the result and a boolean status. +func Add(a, b int) (int, bool) { + if _is64Bit() { + r64, ok := Add64(int64(a), int64(b)) + return int(r64), ok + } + r32, ok := Add32(int32(a), int32(b)) + return int(r32), ok +} + +// Sub returns the difference of two ints and a boolean status. +func Sub(a, b int) (int, bool) { + if _is64Bit() { + r64, ok := Sub64(int64(a), int64(b)) + return int(r64), ok + } + r32, ok := Sub32(int32(a), int32(b)) + return int(r32), ok +} + +// Mul returns the product of two ints and a boolean status. +func Mul(a, b int) (int, bool) { + if _is64Bit() { + r64, ok := Mul64(int64(a), int64(b)) + return int(r64), ok + } + r32, ok := Mul32(int32(a), int32(b)) + return int(r32), ok +} + +// Div returns the quotient of two ints and a boolean status +func Div(a, b int) (int, bool) { + if _is64Bit() { + r64, ok := Div64(int64(a), int64(b)) + return int(r64), ok + } + r32, ok := Div32(int32(a), int32(b)) + return int(r32), ok +} + +// Quo returns the quotient, remainder and status of two ints +func Quo(a, b int) (int, int, bool) { + if _is64Bit() { + q64, r64, ok := Quo64(int64(a), int64(b)) + return int(q64), int(r64), ok + } + q32, r32, ok := Quo32(int32(a), int32(b)) + return int(q32), int(r32), ok +} + +/************* Panic versions for int ****************/ + +// Addp returns the sum of two ints, panicking on overflow +func Addp(a, b int) int { + r, ok := Add(a, b) + if !ok { + panic("addition overflow") + } + return r +} + +// Subp returns the difference of two ints, panicking on overflow. +func Subp(a, b int) int { + r, ok := Sub(a, b) + if !ok { + panic("subtraction overflow") + } + return r +} + +// Mulp returns the product of two ints, panicking on overflow. +func Mulp(a, b int) int { + r, ok := Mul(a, b) + if !ok { + panic("multiplication overflow") + } + return r +} + +// Divp returns the quotient of two ints, panicking on overflow. +func Divp(a, b int) int { + r, ok := Div(a, b) + if !ok { + panic("division failure") + } + return r +} + +//---------------------------------------- +// This is generated code, created by overflow_template.sh executed +// by "go generate" + +// Add8 performs + operation on two int8 operands +// returning a result and status +func Add8(a, b int8) (int8, bool) { + c := a + b + if (c > a) == (b > 0) { + return c, true + } + return c, false +} + +// Add8p is the unchecked panicking version of Add8 +func Add8p(a, b int8) int8 { + r, ok := Add8(a, b) + if !ok { + panic("addition overflow") + } + return r +} + +// Sub8 performs - operation on two int8 operands +// returning a result and status +func Sub8(a, b int8) (int8, bool) { + c := a - b + if (c < a) == (b > 0) { + return c, true + } + return c, false +} + +// Sub8p is the unchecked panicking version of Sub8 +func Sub8p(a, b int8) int8 { + r, ok := Sub8(a, b) + if !ok { + panic("subtraction overflow") + } + return r +} + +// Mul8 performs * operation on two int8 operands +// returning a result and status +func Mul8(a, b int8) (int8, bool) { + if a == 0 || b == 0 { + return 0, true + } + c := a * b + if (c < 0) == ((a < 0) != (b < 0)) { + if c/b == a { + return c, true + } + } + return c, false +} + +// Mul8p is the unchecked panicking version of Mul8 +func Mul8p(a, b int8) int8 { + r, ok := Mul8(a, b) + if !ok { + panic("multiplication overflow") + } + return r +} + +// Div8 performs / operation on two int8 operands +// returning a result and status +func Div8(a, b int8) (int8, bool) { + q, _, ok := Quo8(a, b) + return q, ok +} + +// Div8p is the unchecked panicking version of Div8 +func Div8p(a, b int8) int8 { + r, ok := Div8(a, b) + if !ok { + panic("division failure") + } + return r +} + +// Quo8 performs + operation on two int8 operands +// returning a quotient, a remainder and status +func Quo8(a, b int8) (int8, int8, bool) { + if b == 0 { + return 0, 0, false + } else if b == -1 && a == int8(math.MinInt8) { + return 0, 0, false + } + c := a / b + return c, a % b, true +} + +// Add16 performs + operation on two int16 operands +// returning a result and status +func Add16(a, b int16) (int16, bool) { + c := a + b + if (c > a) == (b > 0) { + return c, true + } + return c, false +} + +// Add16p is the unchecked panicking version of Add16 +func Add16p(a, b int16) int16 { + r, ok := Add16(a, b) + if !ok { + panic("addition overflow") + } + return r +} + +// Sub16 performs - operation on two int16 operands +// returning a result and status +func Sub16(a, b int16) (int16, bool) { + c := a - b + if (c < a) == (b > 0) { + return c, true + } + return c, false +} + +// Sub16p is the unchecked panicking version of Sub16 +func Sub16p(a, b int16) int16 { + r, ok := Sub16(a, b) + if !ok { + panic("subtraction overflow") + } + return r +} + +// Mul16 performs * operation on two int16 operands +// returning a result and status +func Mul16(a, b int16) (int16, bool) { + if a == 0 || b == 0 { + return 0, true + } + c := a * b + if (c < 0) == ((a < 0) != (b < 0)) { + if c/b == a { + return c, true + } + } + return c, false +} + +// Mul16p is the unchecked panicking version of Mul16 +func Mul16p(a, b int16) int16 { + r, ok := Mul16(a, b) + if !ok { + panic("multiplication overflow") + } + return r +} + +// Div16 performs / operation on two int16 operands +// returning a result and status +func Div16(a, b int16) (int16, bool) { + q, _, ok := Quo16(a, b) + return q, ok +} + +// Div16p is the unchecked panicking version of Div16 +func Div16p(a, b int16) int16 { + r, ok := Div16(a, b) + if !ok { + panic("division failure") + } + return r +} + +// Quo16 performs + operation on two int16 operands +// returning a quotient, a remainder and status +func Quo16(a, b int16) (int16, int16, bool) { + if b == 0 { + return 0, 0, false + } else if b == -1 && a == int16(math.MinInt16) { + return 0, 0, false + } + c := a / b + return c, a % b, true +} + +// Add32 performs + operation on two int32 operands +// returning a result and status +func Add32(a, b int32) (int32, bool) { + c := a + b + if (c > a) == (b > 0) { + return c, true + } + return c, false +} + +// Add32p is the unchecked panicking version of Add32 +func Add32p(a, b int32) int32 { + r, ok := Add32(a, b) + if !ok { + panic("addition overflow") + } + return r +} + +// Sub32 performs - operation on two int32 operands +// returning a result and status +func Sub32(a, b int32) (int32, bool) { + c := a - b + if (c < a) == (b > 0) { + return c, true + } + return c, false +} + +// Sub32p is the unchecked panicking version of Sub32 +func Sub32p(a, b int32) int32 { + r, ok := Sub32(a, b) + if !ok { + panic("subtraction overflow") + } + return r +} + +// Mul32 performs * operation on two int32 operands +// returning a result and status +func Mul32(a, b int32) (int32, bool) { + if a == 0 || b == 0 { + return 0, true + } + c := a * b + if (c < 0) == ((a < 0) != (b < 0)) { + if c/b == a { + return c, true + } + } + return c, false +} + +// Mul32p is the unchecked panicking version of Mul32 +func Mul32p(a, b int32) int32 { + r, ok := Mul32(a, b) + if !ok { + panic("multiplication overflow") + } + return r +} + +// Div32 performs / operation on two int32 operands +// returning a result and status +func Div32(a, b int32) (int32, bool) { + q, _, ok := Quo32(a, b) + return q, ok +} + +// Div32p is the unchecked panicking version of Div32 +func Div32p(a, b int32) int32 { + r, ok := Div32(a, b) + if !ok { + panic("division failure") + } + return r +} + +// Quo32 performs + operation on two int32 operands +// returning a quotient, a remainder and status +func Quo32(a, b int32) (int32, int32, bool) { + if b == 0 { + return 0, 0, false + } else if b == -1 && a == int32(math.MinInt32) { + return 0, 0, false + } + c := a / b + return c, a % b, true +} + +// Add64 performs + operation on two int64 operands +// returning a result and status +func Add64(a, b int64) (int64, bool) { + c := a + b + if (c > a) == (b > 0) { + return c, true + } + return c, false +} + +// Add64p is the unchecked panicking version of Add64 +func Add64p(a, b int64) int64 { + r, ok := Add64(a, b) + if !ok { + panic("addition overflow") + } + return r +} + +// Sub64 performs - operation on two int64 operands +// returning a result and status +func Sub64(a, b int64) (int64, bool) { + c := a - b + if (c < a) == (b > 0) { + return c, true + } + return c, false +} + +// Sub64p is the unchecked panicking version of Sub64 +func Sub64p(a, b int64) int64 { + r, ok := Sub64(a, b) + if !ok { + panic("subtraction overflow") + } + return r +} + +// Mul64 performs * operation on two int64 operands +// returning a result and status +func Mul64(a, b int64) (int64, bool) { + if a == 0 || b == 0 { + return 0, true + } + c := a * b + if (c < 0) == ((a < 0) != (b < 0)) { + if c/b == a { + return c, true + } + } + return c, false +} + +// Mul64p is the unchecked panicking version of Mul64 +func Mul64p(a, b int64) int64 { + r, ok := Mul64(a, b) + if !ok { + panic("multiplication overflow") + } + return r +} + +// Div64 performs / operation on two int64 operands +// returning a result and status +func Div64(a, b int64) (int64, bool) { + q, _, ok := Quo64(a, b) + return q, ok +} + +// Div64p is the unchecked panicking version of Div64 +func Div64p(a, b int64) int64 { + r, ok := Div64(a, b) + if !ok { + panic("division failure") + } + return r +} + +// Quo64 performs + operation on two int64 operands +// returning a quotient, a remainder and status +func Quo64(a, b int64) (int64, int64, bool) { + if b == 0 { + return 0, 0, false + } else if b == -1 && a == math.MinInt64 { + return 0, 0, false + } + c := a / b + return c, a % b, true +} diff --git a/gnovm/stdlibs/math/overflow/overflow_test.gno b/gnovm/stdlibs/math/overflow/overflow_test.gno new file mode 100644 index 00000000000..b7881aec480 --- /dev/null +++ b/gnovm/stdlibs/math/overflow/overflow_test.gno @@ -0,0 +1,200 @@ +package overflow + +import ( + "math" + "testing" +) + +// sample all possibilities of 8 bit numbers +// by checking against 64 bit numbers + +func TestAlgorithms(t *testing.T) { + errors := 0 + + for a64 := int64(math.MinInt8); a64 <= int64(math.MaxInt8); a64++ { + for b64 := int64(math.MinInt8); b64 <= int64(math.MaxInt8) && errors < 10; b64++ { + + a8 := int8(a64) + b8 := int8(b64) + + if int64(a8) != a64 || int64(b8) != b64 { + t.Fatal("LOGIC FAILURE IN TEST") + } + + // ADDITION + { + r64 := a64 + b64 + + // now the verification + result, ok := Add8(a8, b8) + if ok && int64(result) != r64 { + t.Errorf("failed to fail on %v + %v = %v instead of %v\n", + a8, b8, result, r64) + errors++ + } + if !ok && int64(result) == r64 { + t.Fail() + errors++ + } + } + + // SUBTRACTION + { + r64 := a64 - b64 + + // now the verification + result, ok := Sub8(a8, b8) + if ok && int64(result) != r64 { + t.Errorf("failed to fail on %v - %v = %v instead of %v\n", + a8, b8, result, r64) + } + if !ok && int64(result) == r64 { + t.Fail() + errors++ + } + } + + // MULTIPLICATION + { + r64 := a64 * b64 + + // now the verification + result, ok := Mul8(a8, b8) + if ok && int64(result) != r64 { + t.Errorf("failed to fail on %v * %v = %v instead of %v\n", + a8, b8, result, r64) + errors++ + } + if !ok && int64(result) == r64 { + t.Fail() + errors++ + } + } + + // DIVISION + if b8 != 0 { + r64 := a64 / b64 + rem64 := a64 % b64 + + // now the verification + result, rem, ok := Quo8(a8, b8) + if ok && int64(result) != r64 { + t.Errorf("failed to fail on %v / %v = %v instead of %v\n", + a8, b8, result, r64) + errors++ + } + if ok && int64(rem) != rem64 { + t.Errorf("failed to fail on %v %% %v = %v instead of %v\n", + a8, b8, rem, rem64) + errors++ + } + } + } + } +} + +func TestQuotient(t *testing.T) { + q, r, ok := Quo(100, 3) + if r != 1 || q != 33 || !ok { + t.Errorf("expected 100/3 => 33, r=1") + } + if _, _, ok = Quo(1, 0); ok { + t.Error("unexpected lack of failure") + } +} + +func TestLong(t *testing.T) { + if testing.Short() { + t.Skip() + } + + ctr := int64(0) + + for a64 := int64(math.MinInt16); a64 <= int64(math.MaxInt16); a64++ { + for b64 := int64(math.MinInt16); b64 <= int64(math.MaxInt16); b64++ { + a16 := int16(a64) + b16 := int16(b64) + if int64(a16) != a64 || int64(b16) != b64 { + panic("LOGIC FAILURE IN TEST") + } + ctr++ + + // ADDITION + { + r64 := a64 + b64 + + // now the verification + result, ok := Add16(a16, b16) + if int64(math.MinInt16) <= r64 && r64 <= int64(math.MaxInt16) { + if !ok || int64(result) != r64 { + println("add", a16, b16, result, r64) + panic("incorrect result for non-overflow") + } + } else { + if ok { + println("add", a16, b16, result, r64) + panic("incorrect ok result") + } + } + } + + // SUBTRACTION + { + r64 := a64 - b64 + + // now the verification + result, ok := Sub16(a16, b16) + if int64(math.MinInt16) <= r64 && r64 <= int64(math.MaxInt16) { + if !ok || int64(result) != r64 { + println("sub", a16, b16, result, r64) + panic("incorrect result for non-overflow") + } + } else { + if ok { + println("sub", a16, b16, result, r64) + panic("incorrect ok result") + } + } + } + + // MULTIPLICATION + { + r64 := a64 * b64 + + // now the verification + result, ok := Mul16(a16, b16) + if int64(math.MinInt16) <= r64 && r64 <= int64(math.MaxInt16) { + if !ok || int64(result) != r64 { + println("mul", a16, b16, result, r64) + panic("incorrect result for non-overflow") + } + } else { + if ok { + println("mul", a16, b16, result, r64) + panic("incorrect ok result") + } + } + } + + // DIVISION + if b16 != 0 { + r64 := a64 / b64 + + // now the verification + result, _, ok := Quo16(a16, b16) + if int64(math.MinInt16) <= r64 && r64 <= int64(math.MaxInt16) { + if !ok || int64(result) != r64 { + println("quo", a16, b16, result, r64) + panic("incorrect result for non-overflow") + } + } else { + if ok { + println("quo", a16, b16, result, r64) + panic("incorrect ok result") + } + } + } + } + } + println("done", ctr) +} diff --git a/gnovm/stdlibs/std/coins.gno b/gnovm/stdlibs/std/coins.gno index 679674e443e..47e88e238d2 100644 --- a/gnovm/stdlibs/std/coins.gno +++ b/gnovm/stdlibs/std/coins.gno @@ -1,6 +1,9 @@ package std -import "strconv" +import ( + "math/overflow" + "strconv" +) // NOTE: this is selectively copied over from tm2/pkgs/std/coin.go @@ -53,7 +56,13 @@ func (c Coin) IsEqual(other Coin) bool { // An invalid result panics. func (c Coin) Add(other Coin) Coin { mustMatchDenominations(c.Denom, other.Denom) - c.Amount += other.Amount + + sum, ok := overflow.Add64(c.Amount, other.Amount) + if !ok { + panic("coin add overflow/underflow: " + strconv.Itoa(int(c.Amount)) + " +/- " + strconv.Itoa(int(other.Amount))) + } + + c.Amount = sum return c } @@ -63,7 +72,13 @@ func (c Coin) Add(other Coin) Coin { // An invalid result panics. func (c Coin) Sub(other Coin) Coin { mustMatchDenominations(c.Denom, other.Denom) - c.Amount -= other.Amount + + dff, ok := overflow.Sub64(c.Amount, other.Amount) + if !ok { + panic("coin sub overflow/underflow: " + strconv.Itoa(int(c.Amount)) + " +/- " + strconv.Itoa(int(other.Amount))) + } + c.Amount = dff + return c } @@ -98,7 +113,10 @@ func NewCoins(coins ...Coin) Coins { for _, coin := range coins { if currentAmount, exists := coinMap[coin.Denom]; exists { - coinMap[coin.Denom] = currentAmount + coin.Amount + var ok bool + if coinMap[coin.Denom], ok = overflow.Add64(currentAmount, coin.Amount); !ok { + panic("coin sub overflow/underflow: " + strconv.Itoa(int(currentAmount)) + " +/- " + strconv.Itoa(int(coin.Amount))) + } } else { coinMap[coin.Denom] = coin.Amount } diff --git a/gnovm/tests/files/overflow0.gno b/gnovm/tests/files/overflow0.gno deleted file mode 100644 index 1313f064322..00000000000 --- a/gnovm/tests/files/overflow0.gno +++ /dev/null @@ -1,10 +0,0 @@ -package main - -func main() { - var a, b, c int8 = -1<<7, -1, 0 - c = a / b // overflow: -128 instead of 128 - println(c) -} - -// Error: -// division by zero or overflow diff --git a/gnovm/tests/files/overflow1.gno b/gnovm/tests/files/overflow1.gno deleted file mode 100644 index a416e9a3498..00000000000 --- a/gnovm/tests/files/overflow1.gno +++ /dev/null @@ -1,10 +0,0 @@ -package main - -func main() { - var a, b, c int16 = -1<<15, -1, 0 - c = a / b // overflow: -32768 instead of 32768 - println(c) -} - -// Error: -// division by zero or overflow diff --git a/gnovm/tests/files/overflow2.gno b/gnovm/tests/files/overflow2.gno deleted file mode 100644 index 353729bcdf2..00000000000 --- a/gnovm/tests/files/overflow2.gno +++ /dev/null @@ -1,10 +0,0 @@ -package main - -func main() { - var a, b, c int32 = -1<<31, -1, 0 - c = a / b // overflow: -2147483648 instead of 2147483648 - println(c) -} - -// Error: -// division by zero or overflow diff --git a/gnovm/tests/files/overflow3.gno b/gnovm/tests/files/overflow3.gno deleted file mode 100644 index a09c59dfb03..00000000000 --- a/gnovm/tests/files/overflow3.gno +++ /dev/null @@ -1,10 +0,0 @@ -package main - -func main() { - var a, b, c int64 = -1<<63, -1, 0 - c = a / b // overflow: -9223372036854775808 instead of 9223372036854775808 - println(c) -} - -// Error: -// division by zero or overflow diff --git a/gnovm/tests/files/overflow4.gno b/gnovm/tests/files/overflow4.gno deleted file mode 100644 index 26b05567b07..00000000000 --- a/gnovm/tests/files/overflow4.gno +++ /dev/null @@ -1,10 +0,0 @@ -package main - -func main() { - var a, b, c int = -1<<63, -1, 0 - c = a / b // overflow: -9223372036854775808 instead of 9223372036854775808 - println(c) -} - -// Error: -// division by zero or overflow diff --git a/gnovm/tests/files/overflow5.gno b/gnovm/tests/files/overflow5.gno deleted file mode 100644 index ef7f976eb24..00000000000 --- a/gnovm/tests/files/overflow5.gno +++ /dev/null @@ -1,10 +0,0 @@ -package main - -func main() { - var a, b, c int = -5, 7, 0 - c = a % b // 0 quotient triggers a false negative in gnolang/overflow - println(c) -} - -// Output: -// -5 diff --git a/gnovm/tests/files/recover14.gno b/gnovm/tests/files/recover14.gno index 3c96404fcbe..30a34ab291a 100644 --- a/gnovm/tests/files/recover14.gno +++ b/gnovm/tests/files/recover14.gno @@ -12,4 +12,4 @@ func main() { } // Output: -// recover: division by zero or overflow +// recover: division by zero diff --git a/misc/genstd/util.go b/misc/genstd/util.go index 13e90836f36..025fe4b673e 100644 --- a/misc/genstd/util.go +++ b/misc/genstd/util.go @@ -70,8 +70,7 @@ func findDirs() (gitRoot string, relPath string, err error) { } p := wd for { - // .git is normally a directory, or a file in case of a git worktree. - if _, e := os.Stat(filepath.Join(p, ".git")); e == nil { + if s, e := os.Stat(filepath.Join(p, ".git")); e == nil && s.IsDir() { // make relPath relative to the git root rp := strings.TrimPrefix(wd, p+string(filepath.Separator)) // normalize separator to / From 6909efaac0d4211c3ce894e052fd0d7b90112809 Mon Sep 17 00:00:00 2001 From: Morgan Date: Tue, 14 Jan 2025 17:23:39 +0100 Subject: [PATCH 08/20] refactor(gnovm): move go type checking to own file (#3491) --- gnovm/pkg/gnolang/go2gno.go | 173 ------------- gnovm/pkg/gnolang/go2gno_test.go | 352 ------------------------- gnovm/pkg/gnolang/gotypecheck.go | 179 +++++++++++++ gnovm/pkg/gnolang/gotypecheck_test.go | 359 ++++++++++++++++++++++++++ 4 files changed, 538 insertions(+), 525 deletions(-) create mode 100644 gnovm/pkg/gnolang/gotypecheck.go create mode 100644 gnovm/pkg/gnolang/gotypecheck_test.go diff --git a/gnovm/pkg/gnolang/go2gno.go b/gnovm/pkg/gnolang/go2gno.go index 82d5c69b08b..4c9de87a6a7 100644 --- a/gnovm/pkg/gnolang/go2gno.go +++ b/gnovm/pkg/gnolang/go2gno.go @@ -31,24 +31,16 @@ package gnolang */ import ( - "bytes" "fmt" "go/ast" - "go/format" "go/parser" "go/token" - "go/types" "os" - "path" "reflect" - "slices" "strconv" - "strings" "github.com/davecgh/go-spew/spew" - "github.com/gnolang/gno/gnovm" "github.com/gnolang/gno/tm2/pkg/errors" - "go.uber.org/multierr" ) func MustReadFile(path string) *FileNode { @@ -483,171 +475,6 @@ func Go2Gno(fs *token.FileSet, gon ast.Node) (n Node) { } } -//---------------------------------------- -// type checking (using go/types) -// XXX move to gotypecheck.go. - -// MemPackageGetter implements the GetMemPackage() method. It is a subset of -// [Store], separated for ease of testing. -type MemPackageGetter interface { - GetMemPackage(path string) *gnovm.MemPackage -} - -// TypeCheckMemPackage performs type validation and checking on the given -// mempkg. To retrieve dependencies, it uses getter. -// -// The syntax checking is performed entirely using Go's go/types package. -// -// If format is true, the code will be automatically updated with the -// formatted source code. -func TypeCheckMemPackage(mempkg *gnovm.MemPackage, getter MemPackageGetter, format bool) error { - return typeCheckMemPackage(mempkg, getter, false, format) -} - -// TypeCheckMemPackageTest performs the same type checks as [TypeCheckMemPackage], -// but allows re-declarations. -// -// Note: like TypeCheckMemPackage, this function ignores tests and filetests. -func TypeCheckMemPackageTest(mempkg *gnovm.MemPackage, getter MemPackageGetter) error { - return typeCheckMemPackage(mempkg, getter, true, false) -} - -func typeCheckMemPackage(mempkg *gnovm.MemPackage, getter MemPackageGetter, testing, format bool) error { - var errs error - imp := &gnoImporter{ - getter: getter, - cache: map[string]gnoImporterResult{}, - cfg: &types.Config{ - Error: func(err error) { - errs = multierr.Append(errs, err) - }, - }, - allowRedefinitions: testing, - } - imp.cfg.Importer = imp - - _, err := imp.parseCheckMemPackage(mempkg, format) - // prefer to return errs instead of err: - // err will generally contain only the first error encountered. - if errs != nil { - return errs - } - return err -} - -type gnoImporterResult struct { - pkg *types.Package - err error -} - -type gnoImporter struct { - getter MemPackageGetter - cache map[string]gnoImporterResult - cfg *types.Config - - // allow symbol redefinitions? (test standard libraries) - allowRedefinitions bool -} - -// Unused, but satisfies the Importer interface. -func (g *gnoImporter) Import(path string) (*types.Package, error) { - return g.ImportFrom(path, "", 0) -} - -type importNotFoundError string - -func (e importNotFoundError) Error() string { return "import not found: " + string(e) } - -// ImportFrom returns the imported package for the given import -// path when imported by a package file located in dir. -func (g *gnoImporter) ImportFrom(path, _ string, _ types.ImportMode) (*types.Package, error) { - if pkg, ok := g.cache[path]; ok { - return pkg.pkg, pkg.err - } - mpkg := g.getter.GetMemPackage(path) - if mpkg == nil { - err := importNotFoundError(path) - g.cache[path] = gnoImporterResult{err: err} - return nil, err - } - fmt := false - result, err := g.parseCheckMemPackage(mpkg, fmt) - g.cache[path] = gnoImporterResult{pkg: result, err: err} - return result, err -} - -func (g *gnoImporter) parseCheckMemPackage(mpkg *gnovm.MemPackage, fmt bool) (*types.Package, error) { - // This map is used to allow for function re-definitions, which are allowed - // in Gno (testing context) but not in Go. - // This map links each function identifier with a closure to remove its - // associated declaration. - var delFunc map[string]func() - if g.allowRedefinitions { - delFunc = make(map[string]func()) - } - - fset := token.NewFileSet() - files := make([]*ast.File, 0, len(mpkg.Files)) - var errs error - for _, file := range mpkg.Files { - // Ignore non-gno files. - // TODO: support filetest type checking. (should probably handle as each its - // own separate pkg, which should also be typechecked) - if !strings.HasSuffix(file.Name, ".gno") || - strings.HasSuffix(file.Name, "_test.gno") || - strings.HasSuffix(file.Name, "_filetest.gno") { - continue - } - - const parseOpts = parser.ParseComments | parser.DeclarationErrors | parser.SkipObjectResolution - f, err := parser.ParseFile(fset, path.Join(mpkg.Path, file.Name), file.Body, parseOpts) - if err != nil { - errs = multierr.Append(errs, err) - continue - } - - if delFunc != nil { - deleteOldIdents(delFunc, f) - } - - // enforce formatting - if fmt { - var buf bytes.Buffer - err = format.Node(&buf, fset, f) - if err != nil { - errs = multierr.Append(errs, err) - continue - } - file.Body = buf.String() - } - - files = append(files, f) - } - if errs != nil { - return nil, errs - } - - return g.cfg.Check(mpkg.Path, fset, files, nil) -} - -func deleteOldIdents(idents map[string]func(), f *ast.File) { - for _, decl := range f.Decls { - fd, ok := decl.(*ast.FuncDecl) - if !ok || fd.Recv != nil { // ignore methods - continue - } - if del := idents[fd.Name.Name]; del != nil { - del() - } - decl := decl - idents[fd.Name.Name] = func() { - // NOTE: cannot use the index as a file may contain multiple decls to be removed, - // so removing one would make all "later" indexes wrong. - f.Decls = slices.DeleteFunc(f.Decls, func(d ast.Decl) bool { return decl == d }) - } - } -} - //---------------------------------------- // utility methods diff --git a/gnovm/pkg/gnolang/go2gno_test.go b/gnovm/pkg/gnolang/go2gno_test.go index 8aba5d7f293..920c5acd9c8 100644 --- a/gnovm/pkg/gnolang/go2gno_test.go +++ b/gnovm/pkg/gnolang/go2gno_test.go @@ -4,10 +4,7 @@ import ( "fmt" "testing" - "github.com/gnolang/gno/gnovm" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/multierr" ) func TestParseForLoop(t *testing.T) { @@ -28,352 +25,3 @@ func main(){ fmt.Printf("AST:\n%#v\n\n", n) fmt.Printf("AST.String():\n%s\n", n.String()) } - -type mockPackageGetter []*gnovm.MemPackage - -func (mi mockPackageGetter) GetMemPackage(path string) *gnovm.MemPackage { - for _, pkg := range mi { - if pkg.Path == path { - return pkg - } - } - return nil -} - -type mockPackageGetterCounts struct { - mockPackageGetter - counts map[string]int -} - -func (mpg mockPackageGetterCounts) GetMemPackage(path string) *gnovm.MemPackage { - mpg.counts[path]++ - return mpg.mockPackageGetter.GetMemPackage(path) -} - -func TestTypeCheckMemPackage(t *testing.T) { - t.Parallel() - - // if len(ss) > 0, then multierr.Errors must decompose it in errors, and - // each error in order must contain the associated string. - errContains := func(s0 string, ss ...string) func(*testing.T, error) { - return func(t *testing.T, err error) { - t.Helper() - errs := multierr.Errors(err) - if len(errs) == 0 { - t.Errorf("expected an error, got nil") - return - } - want := len(ss) + 1 - if len(errs) != want { - t.Errorf("expected %d errors, got %d", want, len(errs)) - return - } - assert.ErrorContains(t, errs[0], s0) - for idx, err := range errs[1:] { - assert.ErrorContains(t, err, ss[idx]) - } - } - } - - type testCase struct { - name string - pkg *gnovm.MemPackage - getter MemPackageGetter - check func(*testing.T, error) - } - tt := []testCase{ - { - "Simple", - &gnovm.MemPackage{ - Name: "hello", - Path: "gno.land/p/demo/hello", - Files: []*gnovm.MemFile{ - { - Name: "hello.gno", - Body: ` - package hello - type S struct{} - func A() S { return S{} } - func B() S { return A() }`, - }, - }, - }, - nil, - nil, - }, - { - "WrongReturn", - &gnovm.MemPackage{ - Name: "hello", - Path: "gno.land/p/demo/hello", - Files: []*gnovm.MemFile{ - { - Name: "hello.gno", - Body: ` - package hello - type S struct{} - func A() S { return S{} } - func B() S { return 11 }`, - }, - }, - }, - nil, - errContains("cannot use 11"), - }, - { - "ParseError", - &gnovm.MemPackage{ - Name: "hello", - Path: "gno.land/p/demo/hello", - Files: []*gnovm.MemFile{ - { - Name: "hello.gno", - Body: ` - package hello! - func B() int { return 11 }`, - }, - }, - }, - nil, - errContains("found '!'"), - }, - { - "MultiError", - &gnovm.MemPackage{ - Name: "main", - Path: "gno.land/p/demo/main", - Files: []*gnovm.MemFile{ - { - Name: "hello.gno", - Body: ` - package main - func main() { - _, _ = 11 - return 88, 88 - }`, - }, - }, - }, - nil, - errContains("assignment mismatch", "too many return values"), - }, - { - "TestsIgnored", - &gnovm.MemPackage{ - Name: "hello", - Path: "gno.land/p/demo/hello", - Files: []*gnovm.MemFile{ - { - Name: "hello.gno", - Body: ` - package hello - func B() int { return 11 }`, - }, - { - Name: "hello_test.gno", - Body: `This is not valid Gno code, but it doesn't matter because test - files are not checked.`, - }, - }, - }, - nil, - nil, - }, - { - "ImportFailed", - &gnovm.MemPackage{ - Name: "hello", - Path: "gno.land/p/demo/hello", - Files: []*gnovm.MemFile{ - { - Name: "hello.gno", - Body: ` - package hello - import "std" - func Hello() std.Address { return "hello" }`, - }, - }, - }, - mockPackageGetter{}, - errContains("import not found: std"), - }, - { - "ImportSucceeded", - &gnovm.MemPackage{ - Name: "hello", - Path: "gno.land/p/demo/hello", - Files: []*gnovm.MemFile{ - { - Name: "hello.gno", - Body: ` - package hello - import "std" - func Hello() std.Address { return "hello" }`, - }, - }, - }, - mockPackageGetter{ - &gnovm.MemPackage{ - Name: "std", - Path: "std", - Files: []*gnovm.MemFile{ - { - Name: "gnovm.gno", - Body: ` - package std - type Address string`, - }, - }, - }, - }, - nil, - }, - { - "ImportBadIdent", - &gnovm.MemPackage{ - Name: "hello", - Path: "gno.land/p/demo/hello", - Files: []*gnovm.MemFile{ - { - Name: "hello.gno", - Body: ` - package hello - import "std" - func Hello() std.Address { return "hello" }`, - }, - }, - }, - mockPackageGetter{ - &gnovm.MemPackage{ - Name: "a_completely_different_identifier", - Path: "std", - Files: []*gnovm.MemFile{ - { - Name: "gnovm.gno", - Body: ` - package a_completely_different_identifier - type Address string`, - }, - }, - }, - }, - errContains("undefined: std", "a_completely_different_identifier and not used"), - }, - } - - cacheMpg := mockPackageGetterCounts{ - mockPackageGetter{ - &gnovm.MemPackage{ - Name: "bye", - Path: "bye", - Files: []*gnovm.MemFile{ - { - Name: "bye.gno", - Body: ` - package bye - import "std" - func Bye() std.Address { return "bye" }`, - }, - }, - }, - &gnovm.MemPackage{ - Name: "std", - Path: "std", - Files: []*gnovm.MemFile{ - { - Name: "gnovm.gno", - Body: ` - package std - type Address string`, - }, - }, - }, - }, - make(map[string]int), - } - - tt = append(tt, testCase{ - "ImportWithCache", - // This test will make use of the importer's internal cache for package `std`. - &gnovm.MemPackage{ - Name: "hello", - Path: "gno.land/p/demo/hello", - Files: []*gnovm.MemFile{ - { - Name: "hello.gno", - Body: ` - package hello - import ( - "std" - "bye" - ) - func Hello() std.Address { return bye.Bye() }`, - }, - }, - }, - cacheMpg, - func(t *testing.T, err error) { - t.Helper() - require.NoError(t, err) - assert.Equal(t, map[string]int{"std": 1, "bye": 1}, cacheMpg.counts) - }, - }) - - for _, tc := range tt { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - format := false - err := TypeCheckMemPackage(tc.pkg, tc.getter, format) - if tc.check == nil { - assert.NoError(t, err) - } else { - tc.check(t, err) - } - }) - } -} - -func TestTypeCheckMemPackage_format(t *testing.T) { - t.Parallel() - - input := ` - package hello - func Hello(name string) string {return "hello" + name -} - - - -` - - pkg := &gnovm.MemPackage{ - Name: "hello", - Path: "gno.land/p/demo/hello", - Files: []*gnovm.MemFile{ - { - Name: "hello.gno", - Body: input, - }, - }, - } - - mpkgGetter := mockPackageGetter{} - format := false - err := TypeCheckMemPackage(pkg, mpkgGetter, format) - assert.NoError(t, err) - assert.Equal(t, input, pkg.Files[0].Body) // unchanged - - expected := `package hello - -func Hello(name string) string { - return "hello" + name -} -` - - format = true - err = TypeCheckMemPackage(pkg, mpkgGetter, format) - assert.NoError(t, err) - assert.NotEqual(t, input, pkg.Files[0].Body) - assert.Equal(t, expected, pkg.Files[0].Body) -} diff --git a/gnovm/pkg/gnolang/gotypecheck.go b/gnovm/pkg/gnolang/gotypecheck.go new file mode 100644 index 00000000000..8f86deb3dc5 --- /dev/null +++ b/gnovm/pkg/gnolang/gotypecheck.go @@ -0,0 +1,179 @@ +package gnolang + +import ( + "bytes" + "go/ast" + "go/format" + "go/parser" + "go/token" + "go/types" + "path" + "slices" + "strings" + + "github.com/gnolang/gno/gnovm" + "go.uber.org/multierr" +) + +// type checking (using go/types) + +// MemPackageGetter implements the GetMemPackage() method. It is a subset of +// [Store], separated for ease of testing. +type MemPackageGetter interface { + GetMemPackage(path string) *gnovm.MemPackage +} + +// TypeCheckMemPackage performs type validation and checking on the given +// mempkg. To retrieve dependencies, it uses getter. +// +// The syntax checking is performed entirely using Go's go/types package. +// +// If format is true, the code will be automatically updated with the +// formatted source code. +func TypeCheckMemPackage(mempkg *gnovm.MemPackage, getter MemPackageGetter, format bool) error { + return typeCheckMemPackage(mempkg, getter, false, format) +} + +// TypeCheckMemPackageTest performs the same type checks as [TypeCheckMemPackage], +// but allows re-declarations. +// +// Note: like TypeCheckMemPackage, this function ignores tests and filetests. +func TypeCheckMemPackageTest(mempkg *gnovm.MemPackage, getter MemPackageGetter) error { + return typeCheckMemPackage(mempkg, getter, true, false) +} + +func typeCheckMemPackage(mempkg *gnovm.MemPackage, getter MemPackageGetter, testing, format bool) error { + var errs error + imp := &gnoImporter{ + getter: getter, + cache: map[string]gnoImporterResult{}, + cfg: &types.Config{ + Error: func(err error) { + errs = multierr.Append(errs, err) + }, + }, + allowRedefinitions: testing, + } + imp.cfg.Importer = imp + + _, err := imp.parseCheckMemPackage(mempkg, format) + // prefer to return errs instead of err: + // err will generally contain only the first error encountered. + if errs != nil { + return errs + } + return err +} + +type gnoImporterResult struct { + pkg *types.Package + err error +} + +type gnoImporter struct { + getter MemPackageGetter + cache map[string]gnoImporterResult + cfg *types.Config + + // allow symbol redefinitions? (test standard libraries) + allowRedefinitions bool +} + +// Unused, but satisfies the Importer interface. +func (g *gnoImporter) Import(path string) (*types.Package, error) { + return g.ImportFrom(path, "", 0) +} + +type importNotFoundError string + +func (e importNotFoundError) Error() string { return "import not found: " + string(e) } + +// ImportFrom returns the imported package for the given import +// path when imported by a package file located in dir. +func (g *gnoImporter) ImportFrom(path, _ string, _ types.ImportMode) (*types.Package, error) { + if pkg, ok := g.cache[path]; ok { + return pkg.pkg, pkg.err + } + mpkg := g.getter.GetMemPackage(path) + if mpkg == nil { + err := importNotFoundError(path) + g.cache[path] = gnoImporterResult{err: err} + return nil, err + } + fmt := false + result, err := g.parseCheckMemPackage(mpkg, fmt) + g.cache[path] = gnoImporterResult{pkg: result, err: err} + return result, err +} + +func (g *gnoImporter) parseCheckMemPackage(mpkg *gnovm.MemPackage, fmt bool) (*types.Package, error) { + // This map is used to allow for function re-definitions, which are allowed + // in Gno (testing context) but not in Go. + // This map links each function identifier with a closure to remove its + // associated declaration. + var delFunc map[string]func() + if g.allowRedefinitions { + delFunc = make(map[string]func()) + } + + fset := token.NewFileSet() + files := make([]*ast.File, 0, len(mpkg.Files)) + var errs error + for _, file := range mpkg.Files { + // Ignore non-gno files. + // TODO: support filetest type checking. (should probably handle as each its + // own separate pkg, which should also be typechecked) + if !strings.HasSuffix(file.Name, ".gno") || + strings.HasSuffix(file.Name, "_test.gno") || + strings.HasSuffix(file.Name, "_filetest.gno") { + continue + } + + const parseOpts = parser.ParseComments | parser.DeclarationErrors | parser.SkipObjectResolution + f, err := parser.ParseFile(fset, path.Join(mpkg.Path, file.Name), file.Body, parseOpts) + if err != nil { + errs = multierr.Append(errs, err) + continue + } + + if delFunc != nil { + deleteOldIdents(delFunc, f) + } + + // enforce formatting + if fmt { + var buf bytes.Buffer + err = format.Node(&buf, fset, f) + if err != nil { + errs = multierr.Append(errs, err) + continue + } + file.Body = buf.String() + } + + files = append(files, f) + } + if errs != nil { + return nil, errs + } + + return g.cfg.Check(mpkg.Path, fset, files, nil) +} + +func deleteOldIdents(idents map[string]func(), f *ast.File) { + for _, decl := range f.Decls { + fd, ok := decl.(*ast.FuncDecl) + if !ok || fd.Recv != nil { // ignore methods + continue + } + if del := idents[fd.Name.Name]; del != nil { + del() + } + decl := decl + idents[fd.Name.Name] = func() { + // NOTE: cannot use the index as a file may contain multiple decls to be removed, + // so removing one would make all "later" indexes wrong. + f.Decls = slices.DeleteFunc(f.Decls, func(d ast.Decl) bool { return decl == d }) + } + } +} diff --git a/gnovm/pkg/gnolang/gotypecheck_test.go b/gnovm/pkg/gnolang/gotypecheck_test.go new file mode 100644 index 00000000000..259a1bc3e78 --- /dev/null +++ b/gnovm/pkg/gnolang/gotypecheck_test.go @@ -0,0 +1,359 @@ +package gnolang + +import ( + "testing" + + "github.com/gnolang/gno/gnovm" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/multierr" +) + +type mockPackageGetter []*gnovm.MemPackage + +func (mi mockPackageGetter) GetMemPackage(path string) *gnovm.MemPackage { + for _, pkg := range mi { + if pkg.Path == path { + return pkg + } + } + return nil +} + +type mockPackageGetterCounts struct { + mockPackageGetter + counts map[string]int +} + +func (mpg mockPackageGetterCounts) GetMemPackage(path string) *gnovm.MemPackage { + mpg.counts[path]++ + return mpg.mockPackageGetter.GetMemPackage(path) +} + +func TestTypeCheckMemPackage(t *testing.T) { + t.Parallel() + + // if len(ss) > 0, then multierr.Errors must decompose it in errors, and + // each error in order must contain the associated string. + errContains := func(s0 string, ss ...string) func(*testing.T, error) { + return func(t *testing.T, err error) { + t.Helper() + errs := multierr.Errors(err) + if len(errs) == 0 { + t.Errorf("expected an error, got nil") + return + } + want := len(ss) + 1 + if len(errs) != want { + t.Errorf("expected %d errors, got %d", want, len(errs)) + return + } + assert.ErrorContains(t, errs[0], s0) + for idx, err := range errs[1:] { + assert.ErrorContains(t, err, ss[idx]) + } + } + } + + type testCase struct { + name string + pkg *gnovm.MemPackage + getter MemPackageGetter + check func(*testing.T, error) + } + tt := []testCase{ + { + "Simple", + &gnovm.MemPackage{ + Name: "hello", + Path: "gno.land/p/demo/hello", + Files: []*gnovm.MemFile{ + { + Name: "hello.gno", + Body: ` + package hello + type S struct{} + func A() S { return S{} } + func B() S { return A() }`, + }, + }, + }, + nil, + nil, + }, + { + "WrongReturn", + &gnovm.MemPackage{ + Name: "hello", + Path: "gno.land/p/demo/hello", + Files: []*gnovm.MemFile{ + { + Name: "hello.gno", + Body: ` + package hello + type S struct{} + func A() S { return S{} } + func B() S { return 11 }`, + }, + }, + }, + nil, + errContains("cannot use 11"), + }, + { + "ParseError", + &gnovm.MemPackage{ + Name: "hello", + Path: "gno.land/p/demo/hello", + Files: []*gnovm.MemFile{ + { + Name: "hello.gno", + Body: ` + package hello! + func B() int { return 11 }`, + }, + }, + }, + nil, + errContains("found '!'"), + }, + { + "MultiError", + &gnovm.MemPackage{ + Name: "main", + Path: "gno.land/p/demo/main", + Files: []*gnovm.MemFile{ + { + Name: "hello.gno", + Body: ` + package main + func main() { + _, _ = 11 + return 88, 88 + }`, + }, + }, + }, + nil, + errContains("assignment mismatch", "too many return values"), + }, + { + "TestsIgnored", + &gnovm.MemPackage{ + Name: "hello", + Path: "gno.land/p/demo/hello", + Files: []*gnovm.MemFile{ + { + Name: "hello.gno", + Body: ` + package hello + func B() int { return 11 }`, + }, + { + Name: "hello_test.gno", + Body: `This is not valid Gno code, but it doesn't matter because test + files are not checked.`, + }, + }, + }, + nil, + nil, + }, + { + "ImportFailed", + &gnovm.MemPackage{ + Name: "hello", + Path: "gno.land/p/demo/hello", + Files: []*gnovm.MemFile{ + { + Name: "hello.gno", + Body: ` + package hello + import "std" + func Hello() std.Address { return "hello" }`, + }, + }, + }, + mockPackageGetter{}, + errContains("import not found: std"), + }, + { + "ImportSucceeded", + &gnovm.MemPackage{ + Name: "hello", + Path: "gno.land/p/demo/hello", + Files: []*gnovm.MemFile{ + { + Name: "hello.gno", + Body: ` + package hello + import "std" + func Hello() std.Address { return "hello" }`, + }, + }, + }, + mockPackageGetter{ + &gnovm.MemPackage{ + Name: "std", + Path: "std", + Files: []*gnovm.MemFile{ + { + Name: "gnovm.gno", + Body: ` + package std + type Address string`, + }, + }, + }, + }, + nil, + }, + { + "ImportBadIdent", + &gnovm.MemPackage{ + Name: "hello", + Path: "gno.land/p/demo/hello", + Files: []*gnovm.MemFile{ + { + Name: "hello.gno", + Body: ` + package hello + import "std" + func Hello() std.Address { return "hello" }`, + }, + }, + }, + mockPackageGetter{ + &gnovm.MemPackage{ + Name: "a_completely_different_identifier", + Path: "std", + Files: []*gnovm.MemFile{ + { + Name: "gnovm.gno", + Body: ` + package a_completely_different_identifier + type Address string`, + }, + }, + }, + }, + errContains("undefined: std", "a_completely_different_identifier and not used"), + }, + } + + cacheMpg := mockPackageGetterCounts{ + mockPackageGetter{ + &gnovm.MemPackage{ + Name: "bye", + Path: "bye", + Files: []*gnovm.MemFile{ + { + Name: "bye.gno", + Body: ` + package bye + import "std" + func Bye() std.Address { return "bye" }`, + }, + }, + }, + &gnovm.MemPackage{ + Name: "std", + Path: "std", + Files: []*gnovm.MemFile{ + { + Name: "gnovm.gno", + Body: ` + package std + type Address string`, + }, + }, + }, + }, + make(map[string]int), + } + + tt = append(tt, testCase{ + "ImportWithCache", + // This test will make use of the importer's internal cache for package `std`. + &gnovm.MemPackage{ + Name: "hello", + Path: "gno.land/p/demo/hello", + Files: []*gnovm.MemFile{ + { + Name: "hello.gno", + Body: ` + package hello + import ( + "std" + "bye" + ) + func Hello() std.Address { return bye.Bye() }`, + }, + }, + }, + cacheMpg, + func(t *testing.T, err error) { + t.Helper() + require.NoError(t, err) + assert.Equal(t, map[string]int{"std": 1, "bye": 1}, cacheMpg.counts) + }, + }) + + for _, tc := range tt { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + format := false + err := TypeCheckMemPackage(tc.pkg, tc.getter, format) + if tc.check == nil { + assert.NoError(t, err) + } else { + tc.check(t, err) + } + }) + } +} + +func TestTypeCheckMemPackage_format(t *testing.T) { + t.Parallel() + + input := ` + package hello + func Hello(name string) string {return "hello" + name +} + + + +` + + pkg := &gnovm.MemPackage{ + Name: "hello", + Path: "gno.land/p/demo/hello", + Files: []*gnovm.MemFile{ + { + Name: "hello.gno", + Body: input, + }, + }, + } + + mpkgGetter := mockPackageGetter{} + format := false + err := TypeCheckMemPackage(pkg, mpkgGetter, format) + assert.NoError(t, err) + assert.Equal(t, input, pkg.Files[0].Body) // unchanged + + expected := `package hello + +func Hello(name string) string { + return "hello" + name +} +` + + format = true + err = TypeCheckMemPackage(pkg, mpkgGetter, format) + assert.NoError(t, err) + assert.NotEqual(t, input, pkg.Files[0].Body) + assert.Equal(t, expected, pkg.Files[0].Body) +} From b3d4855050496222e565377ce2422677a7f1a961 Mon Sep 17 00:00:00 2001 From: Morgan Date: Tue, 14 Jan 2025 18:33:54 +0100 Subject: [PATCH 09/20] test(gnovm): move tests in values_conversions_test.go to filetests (#3490) --- gnovm/pkg/gnolang/values_conversions_test.go | 132 ------------------- gnovm/tests/files/overflow10.gno | 8 ++ gnovm/tests/files/overflow11.gno | 8 ++ gnovm/tests/files/overflow12.gno | 8 ++ gnovm/tests/files/overflow13.gno | 8 ++ gnovm/tests/files/overflow14.gno | 8 ++ gnovm/tests/files/overflow15.gno | 9 ++ gnovm/tests/files/overflow16.gno | 9 ++ gnovm/tests/files/overflow6.gno | 8 ++ gnovm/tests/files/overflow7.gno | 8 ++ gnovm/tests/files/overflow8.gno | 8 ++ gnovm/tests/files/overflow9.gno | 8 ++ 12 files changed, 90 insertions(+), 132 deletions(-) create mode 100644 gnovm/tests/files/overflow10.gno create mode 100644 gnovm/tests/files/overflow11.gno create mode 100644 gnovm/tests/files/overflow12.gno create mode 100644 gnovm/tests/files/overflow13.gno create mode 100644 gnovm/tests/files/overflow14.gno create mode 100644 gnovm/tests/files/overflow15.gno create mode 100644 gnovm/tests/files/overflow16.gno create mode 100644 gnovm/tests/files/overflow6.gno create mode 100644 gnovm/tests/files/overflow7.gno create mode 100644 gnovm/tests/files/overflow8.gno create mode 100644 gnovm/tests/files/overflow9.gno diff --git a/gnovm/pkg/gnolang/values_conversions_test.go b/gnovm/pkg/gnolang/values_conversions_test.go index 5538e973bdc..b8b06df4228 100644 --- a/gnovm/pkg/gnolang/values_conversions_test.go +++ b/gnovm/pkg/gnolang/values_conversions_test.go @@ -2,7 +2,6 @@ package gnolang import ( "math" - "strings" "testing" "github.com/cockroachdb/apd/v3" @@ -27,134 +26,3 @@ func TestConvertUntypedBigdecToFloat(t *testing.T) { require.True(t, softfloat.Feq64(dst.GetFloat64(), 0)) } - -func TestBitShiftingOverflow(t *testing.T) { - t.Parallel() - - testFunc := func(source, msg string) { - defer func() { - if len(msg) == 0 { - return - } - - r := recover() - - if r == nil { - t.Fail() - } - - err := r.(*PreprocessError) - c := strings.Contains(err.Error(), msg) - if !c { - t.Fatalf(`expected "%s", got "%s"`, msg, r) - } - }() - - m := NewMachine("test", nil) - - n := MustParseFile("main.go", source) - m.RunFiles(n) - m.RunMain() - } - - type cases struct { - source string - msg string - } - - tests := []cases{ - { - `package test - -func main() { - const a = int32(1) << 33 -}`, - `test/main.go:3:1: constant overflows`, - }, - { - `package test - -func main() { - const a1 = int8(1) << 8 -}`, - `test/main.go:3:1: constant overflows`, - }, - { - `package test - -func main() { - const a2 = int16(1) << 16 -}`, - `test/main.go:3:1: constant overflows`, - }, - { - `package test - -func main() { - const a3 = int32(1) << 33 -}`, - `test/main.go:3:1: constant overflows`, - }, - { - `package test - -func main() { - const a4 = int64(1) << 65 -}`, - `test/main.go:3:1: constant overflows`, - }, - { - `package test - -func main() { - const b1 = uint8(1) << 8 -}`, - `test/main.go:3:1: constant overflows`, - }, - { - `package test - -func main() { - const b2 = uint16(1) << 16 -}`, - `test/main.go:3:1: constant overflows`, - }, - { - `package test - -func main() { - const b3 = uint32(1) << 33 -}`, - `test/main.go:3:1: constant overflows`, - }, - { - `package test - -func main() { - const b4 = uint64(1) << 65 -}`, - `test/main.go:3:1: constant overflows`, - }, - { - `package test - - func main() { - const c1 = 1 << 128 - }`, - ``, - }, - { - `package test - - func main() { - const c1 = 1 << 128 - println(c1) - }`, - `test/main.go:5:4: bigint overflows target kind`, - }, - } - - for _, tc := range tests { - testFunc(tc.source, tc.msg) - } -} diff --git a/gnovm/tests/files/overflow10.gno b/gnovm/tests/files/overflow10.gno new file mode 100644 index 00000000000..c3224eaf471 --- /dev/null +++ b/gnovm/tests/files/overflow10.gno @@ -0,0 +1,8 @@ +package main + +func main() { + const a4 = int64(1) << 65 +} + +// Error: +// main/files/overflow10.gno:3:1: constant overflows diff --git a/gnovm/tests/files/overflow11.gno b/gnovm/tests/files/overflow11.gno new file mode 100644 index 00000000000..bac883f104f --- /dev/null +++ b/gnovm/tests/files/overflow11.gno @@ -0,0 +1,8 @@ +package main + +func main() { + const b1 = uint8(1) << 8 +} + +// Error: +// main/files/overflow11.gno:3:1: constant overflows diff --git a/gnovm/tests/files/overflow12.gno b/gnovm/tests/files/overflow12.gno new file mode 100644 index 00000000000..52ca8fc80d4 --- /dev/null +++ b/gnovm/tests/files/overflow12.gno @@ -0,0 +1,8 @@ +package main + +func main() { + const b2 = uint16(1) << 16 +} + +// Error: +// main/files/overflow12.gno:3:1: constant overflows diff --git a/gnovm/tests/files/overflow13.gno b/gnovm/tests/files/overflow13.gno new file mode 100644 index 00000000000..40b6885b3ff --- /dev/null +++ b/gnovm/tests/files/overflow13.gno @@ -0,0 +1,8 @@ +package main + +func main() { + const b3 = uint32(1) << 33 +} + +// Error: +// main/files/overflow13.gno:3:1: constant overflows diff --git a/gnovm/tests/files/overflow14.gno b/gnovm/tests/files/overflow14.gno new file mode 100644 index 00000000000..0da66bb7b9d --- /dev/null +++ b/gnovm/tests/files/overflow14.gno @@ -0,0 +1,8 @@ +package main + +func main() { + const b4 = uint64(1) << 65 +} + +// Error: +// main/files/overflow14.gno:3:1: constant overflows diff --git a/gnovm/tests/files/overflow15.gno b/gnovm/tests/files/overflow15.gno new file mode 100644 index 00000000000..f41848d5d21 --- /dev/null +++ b/gnovm/tests/files/overflow15.gno @@ -0,0 +1,9 @@ +package main + +func main() { + const c1 = 1 << 128 + println(uint64(c1 >> 65)) +} + +// Output: +// 9223372036854775808 diff --git a/gnovm/tests/files/overflow16.gno b/gnovm/tests/files/overflow16.gno new file mode 100644 index 00000000000..36cc7689529 --- /dev/null +++ b/gnovm/tests/files/overflow16.gno @@ -0,0 +1,9 @@ +package main + +func main() { + const c1 = 1 << 128 + println(c1) +} + +// Error: +// main/files/overflow16.gno:5:2: bigint overflows target kind diff --git a/gnovm/tests/files/overflow6.gno b/gnovm/tests/files/overflow6.gno new file mode 100644 index 00000000000..b9c49027336 --- /dev/null +++ b/gnovm/tests/files/overflow6.gno @@ -0,0 +1,8 @@ +package main + +func main() { + const a = int32(1) << 33 +} + +// Error: +// main/files/overflow6.gno:3:1: constant overflows diff --git a/gnovm/tests/files/overflow7.gno b/gnovm/tests/files/overflow7.gno new file mode 100644 index 00000000000..28202811fca --- /dev/null +++ b/gnovm/tests/files/overflow7.gno @@ -0,0 +1,8 @@ +package main + +func main() { + const a1 = int8(1) << 8 +} + +// Error: +// main/files/overflow7.gno:3:1: constant overflows diff --git a/gnovm/tests/files/overflow8.gno b/gnovm/tests/files/overflow8.gno new file mode 100644 index 00000000000..f25fb0aa833 --- /dev/null +++ b/gnovm/tests/files/overflow8.gno @@ -0,0 +1,8 @@ +package main + +func main() { + const a2 = int16(1) << 16 +} + +// Error: +// main/files/overflow8.gno:3:1: constant overflows diff --git a/gnovm/tests/files/overflow9.gno b/gnovm/tests/files/overflow9.gno new file mode 100644 index 00000000000..5eae14b67aa --- /dev/null +++ b/gnovm/tests/files/overflow9.gno @@ -0,0 +1,8 @@ +package main + +func main() { + const a3 = int32(1) << 33 +} + +// Error: +// main/files/overflow9.gno:3:1: constant overflows From c79c16dc0d33d260f876b7e034531e180cc0ac86 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 19:21:01 +0100 Subject: [PATCH 10/20] chore(deps): bump anchore/sbom-action from 0.17.8 to 0.17.9 in the actions group (#3476) Bumps the actions group with 1 update: [anchore/sbom-action](https://github.com/anchore/sbom-action). Updates `anchore/sbom-action` from 0.17.8 to 0.17.9
Release notes

Sourced from anchore/sbom-action's releases.

v0.17.9

Changes in v0.17.9

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=anchore/sbom-action&package-manager=github_actions&previous-version=0.17.8&new-version=0.17.9)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/releaser-master.yml | 2 +- .github/workflows/releaser-nightly.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/releaser-master.yml b/.github/workflows/releaser-master.yml index 6e3eed31914..7c81789b060 100644 --- a/.github/workflows/releaser-master.yml +++ b/.github/workflows/releaser-master.yml @@ -27,7 +27,7 @@ jobs: cache: true - uses: sigstore/cosign-installer@v3.7.0 - - uses: anchore/sbom-action/download-syft@v0.17.8 + - uses: anchore/sbom-action/download-syft@v0.17.9 - uses: docker/login-action@v3 with: diff --git a/.github/workflows/releaser-nightly.yml b/.github/workflows/releaser-nightly.yml index 4f6e636af1b..47b6cabb223 100644 --- a/.github/workflows/releaser-nightly.yml +++ b/.github/workflows/releaser-nightly.yml @@ -24,7 +24,7 @@ jobs: cache: true - uses: sigstore/cosign-installer@v3.7.0 - - uses: anchore/sbom-action/download-syft@v0.17.8 + - uses: anchore/sbom-action/download-syft@v0.17.9 - uses: docker/login-action@v3 with: From 3adb2ac390f60467e6d77c3aa7ff4507cb37e0e8 Mon Sep 17 00:00:00 2001 From: 6h057 <15034695+omarsy@users.noreply.github.com> Date: Tue, 14 Jan 2025 21:29:49 +0100 Subject: [PATCH 11/20] feat(gnovm): align Gno constant handling with Go specifications (#2828) Related Issues: Closes #2628 This PR aims to align Gno's constant handling with the Go specification regarding constant expressions (see [Go Specification on Constants](https://go.dev/ref/spec#Constant_expressions)). 1. **Primitive Type Requirement** - **Should Work:** ```go const t = 1 ``` - **Should Return an Error:** ```go const t = []string{"1"} ``` **Error:** ``` (const (slice[("1" string)] []string)) (value of type []string) is not constant ``` 2. **Function Calls Disallowed** Only built-in functions should be allowed. - **Should Work:** ```go const t = len("s") ``` - **Should Return an Error:** ```go func v() string { return "" } const t = v() ``` **Error:** ``` v() (value of type string) is not constant ``` 3. **Constant Operands Requirement** Constant expressions may contain only constant operands and are evaluated at compile time. - **Should Work:** ```go const t = 1 const v = t ``` - **Should Raise an Error:** ```go t := 1 const v = t ``` **Error:** ``` t (variable of type int) is not constant ``` 4. **Type Assertion Forbidden** - This code: ```go var i interface{} = 1 const t, ok = i.(int) ``` - **Should Raise This Error:** ``` i.(int) (comma, ok expression of type int) is not constant ``` 5. **Index Expression Forbidden** - This code: ```go var i = []string{} const t, ok = i[0] ``` - **Should Return This Error:** ``` i[0] (variable of type string) is not constant ```
Contributors' checklist... - [ ] Added new tests, or not needed, or not feasible - [ ] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [ ] Updated the official documentation or not needed - [ ] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [ ] Added references to related issues and PRs - [ ] Provided any useful hints for running manual tests - [ ] Added new benchmarks to [generated graphs](https://gnoland.github.io/benchmarks), if any. More info [here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
--- .../r/demo/keystore/keystore_test.gno | 4 - gnovm/pkg/gnolang/preprocess.go | 1 + gnovm/pkg/gnolang/type_check.go | 145 ++++++++++++++++++ gnovm/tests/files/const23.gno | 11 ++ gnovm/tests/files/const24.gno | 82 ++++++++++ gnovm/tests/files/const25.gno | 11 ++ gnovm/tests/files/const26.gno | 15 ++ gnovm/tests/files/const27.gno | 16 ++ gnovm/tests/files/const28.gno | 12 ++ gnovm/tests/files/const29.gno | 12 ++ gnovm/tests/files/const30.gno | 15 ++ gnovm/tests/files/const31.gno | 15 ++ gnovm/tests/files/const32.gno | 11 ++ gnovm/tests/files/const33.gno | 12 ++ gnovm/tests/files/const34.gno | 10 ++ gnovm/tests/files/const35.gno | 12 ++ gnovm/tests/files/const36.gno | 13 ++ gnovm/tests/files/const37_native.gno | 10 ++ gnovm/tests/files/const37_stdlibs.gno | 10 ++ gnovm/tests/files/const38.gno | 11 ++ gnovm/tests/files/const39.gno | 14 ++ gnovm/tests/files/const40.gno | 8 + gnovm/tests/files/const41.gno | 8 + gnovm/tests/files/const42.gno | 8 + gnovm/tests/files/const43.gno | 10 ++ gnovm/tests/files/const44.gno | 10 ++ gnovm/tests/files/const45_a.gno | 14 ++ gnovm/tests/files/const45_b.gno | 14 ++ gnovm/tests/files/const46.gno | 10 ++ gnovm/tests/files/const47.gno | 10 ++ gnovm/tests/files/const48.gno | 9 ++ gnovm/tests/files/const49.gno | 16 ++ gnovm/tests/files/const50.gno | 12 ++ 33 files changed, 567 insertions(+), 4 deletions(-) create mode 100644 gnovm/tests/files/const23.gno create mode 100644 gnovm/tests/files/const24.gno create mode 100644 gnovm/tests/files/const25.gno create mode 100644 gnovm/tests/files/const26.gno create mode 100644 gnovm/tests/files/const27.gno create mode 100644 gnovm/tests/files/const28.gno create mode 100644 gnovm/tests/files/const29.gno create mode 100644 gnovm/tests/files/const30.gno create mode 100644 gnovm/tests/files/const31.gno create mode 100644 gnovm/tests/files/const32.gno create mode 100644 gnovm/tests/files/const33.gno create mode 100644 gnovm/tests/files/const34.gno create mode 100644 gnovm/tests/files/const35.gno create mode 100644 gnovm/tests/files/const36.gno create mode 100644 gnovm/tests/files/const37_native.gno create mode 100644 gnovm/tests/files/const37_stdlibs.gno create mode 100644 gnovm/tests/files/const38.gno create mode 100644 gnovm/tests/files/const39.gno create mode 100644 gnovm/tests/files/const40.gno create mode 100644 gnovm/tests/files/const41.gno create mode 100644 gnovm/tests/files/const42.gno create mode 100644 gnovm/tests/files/const43.gno create mode 100644 gnovm/tests/files/const44.gno create mode 100644 gnovm/tests/files/const45_a.gno create mode 100644 gnovm/tests/files/const45_b.gno create mode 100644 gnovm/tests/files/const46.gno create mode 100644 gnovm/tests/files/const47.gno create mode 100644 gnovm/tests/files/const48.gno create mode 100644 gnovm/tests/files/const49.gno create mode 100644 gnovm/tests/files/const50.gno diff --git a/examples/gno.land/r/demo/keystore/keystore_test.gno b/examples/gno.land/r/demo/keystore/keystore_test.gno index 41597016ea3..9b5fafa2f95 100644 --- a/examples/gno.land/r/demo/keystore/keystore_test.gno +++ b/examples/gno.land/r/demo/keystore/keystore_test.gno @@ -11,10 +11,6 @@ import ( ) func TestRender(t *testing.T) { - // https://github.com/gnolang/gno/pull/3375 changed const -> var : - // For some reason non native functions fails on constants with - // constant overflows (code=2), this does not happens when using a variable - // TODO: check this issue after and if this PR is merged var ( author1 std.Address = testutils.TestAddress("author1") author2 std.Address = testutils.TestAddress("author2") diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index ddfd1851989..ffa0f518331 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -2294,6 +2294,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { // NOTE: may or may not be a *ConstExpr, // but if not, make one now. for i, vx := range n.Values { + assertValidConstExpr(store, last, n, vx) n.Values[i] = evalConst(store, last, vx) } } else { diff --git a/gnovm/pkg/gnolang/type_check.go b/gnovm/pkg/gnolang/type_check.go index 70166116fcc..f96cb71e4b6 100644 --- a/gnovm/pkg/gnolang/type_check.go +++ b/gnovm/pkg/gnolang/type_check.go @@ -215,6 +215,151 @@ func assertAssignableTo(n Node, xt, dt Type, autoNative bool) { } } +func assertValidConstExpr(store Store, last BlockNode, n *ValueDecl, expr Expr) { + if n.Type != nil { + nt := evalStaticType(store, last, n.Type) + if xnt, ok := nt.(*NativeType); ok { + nt = go2GnoBaseType(xnt.Type) + } + + if _, ok := baseOf(nt).(PrimitiveType); !ok { + panic(fmt.Sprintf("invalid constant type %s", nt.String())) + } + } + + nt := evalStaticTypeOf(store, last, expr) + if xnt, ok := nt.(*NativeType); ok { + nt = go2GnoBaseType(xnt.Type) + } + + if nt == nil { + panic(fmt.Sprintf("%s (variable of type nil) is not constant", expr)) + } + + if _, ok := baseOf(nt).(PrimitiveType); !ok { + panic(fmt.Sprintf("%s (variable of type %s) is not constant", expr, nt)) + } + + assertValidConstValue(store, last, expr) +} + +func assertValidConstValue(store Store, last BlockNode, currExpr Expr) { +Main: + switch currExpr := currExpr.(type) { + case *ConstExpr: + case *UnaryExpr: + // *, & is filter out previously since they are not primitive + assertValidConstValue(store, last, currExpr.X) + case *TypeAssertExpr: + ty := evalStaticTypeOf(store, last, currExpr) + if _, ok := ty.(*TypeType); ok { + ty = evalStaticType(store, last, currExpr) + } + panic(fmt.Sprintf("%s (comma, ok expression of type %s) is not constant", currExpr.String(), currExpr.Type)) + case *CallExpr: + ift := evalStaticTypeOf(store, last, currExpr.Func) + switch baseOf(ift).(type) { + case *FuncType: + tup := evalStaticTypeOfRaw(store, last, currExpr).(*tupleType) + + // check for built-in functions + if cx, ok := currExpr.Func.(*ConstExpr); ok { + if fv, ok := cx.V.(*FuncValue); ok { + if fv.PkgPath == uversePkgPath { + // TODO: should support min, max, real, imag + switch { + case fv.Name == "len": + at := evalStaticTypeOf(store, last, currExpr.Args[0]) + if _, ok := baseOf(at).(*ArrayType); ok { + // ok + break Main + } + assertValidConstValue(store, last, currExpr.Args[0]) + break Main + case fv.Name == "cap": + at := evalStaticTypeOf(store, last, currExpr.Args[0]) + if _, ok := baseOf(at).(*ArrayType); ok { + // ok + break Main + } + assertValidConstValue(store, last, currExpr.Args[0]) + break Main + } + } + } + } + + switch { + case len(tup.Elts) == 0: + panic(fmt.Sprintf("%s (no value) used as value", currExpr.String())) + case len(tup.Elts) == 1: + panic(fmt.Sprintf("%s (value of type %s) is not constant", currExpr.String(), tup.Elts[0])) + default: + panic(fmt.Sprintf("multiple-value %s (value of type %s) in single-value context", currExpr.String(), tup.Elts)) + } + case *TypeType: + for _, arg := range currExpr.Args { + assertValidConstValue(store, last, arg) + } + case *NativeType: + // Todo: should add a test after the fix of https://github.com/gnolang/gno/issues/3006 + ty := evalStaticType(store, last, currExpr.Func) + panic(fmt.Sprintf("%s (variable of type %s) is not constant", currExpr.String(), ty)) + default: + panic(fmt.Sprintf( + "unexpected func type %v (%v)", + ift, reflect.TypeOf(ift))) + } + case *BinaryExpr: + assertValidConstValue(store, last, currExpr.Left) + assertValidConstValue(store, last, currExpr.Right) + case *SelectorExpr: + xt := evalStaticTypeOf(store, last, currExpr.X) + switch xt := xt.(type) { + case *PackageType: + var pv *PackageValue + if cx, ok := currExpr.X.(*ConstExpr); ok { + // NOTE: *Machine.TestMemPackage() needs this + // to pass in an imported package as *ConstEzpr. + pv = cx.V.(*PackageValue) + } else { + // otherwise, packages can only be referred to by + // *NameExprs, and cannot be copied. + pvc := evalConst(store, last, currExpr.X) + pv_, ok := pvc.V.(*PackageValue) + if !ok { + panic(fmt.Sprintf( + "missing package in selector expr %s", + currExpr.String())) + } + pv = pv_ + } + if pv.GetBlock(store).Source.GetIsConst(store, currExpr.Sel) { + break Main + } + + tt := pv.GetBlock(store).Source.GetStaticTypeOf(store, currExpr.Sel) + panic(fmt.Sprintf("%s (variable of type %s) is not constant", currExpr.String(), tt)) + case *PointerType, *DeclaredType, *StructType, *InterfaceType, *TypeType, *NativeType: + ty := evalStaticTypeOf(store, last, currExpr) + if _, ok := ty.(*TypeType); ok { + ty = evalStaticType(store, last, currExpr) + } + panic(fmt.Sprintf("%s (variable of type %s) is not constant", currExpr.String(), ty)) + default: + panic(fmt.Sprintf( + "unexpected selector expression type %v", + reflect.TypeOf(xt))) + } + default: + ift := evalStaticTypeOf(store, last, currExpr) + if _, ok := ift.(*TypeType); ok { + ift = evalStaticType(store, last, currExpr) + } + panic(fmt.Sprintf("%s (variable of type %s) is not constant", currExpr.String(), ift)) + } +} + // checkValDefineMismatch checks for mismatch between the number of variables and values in a ValueDecl or AssignStmt. func checkValDefineMismatch(n Node) { var ( diff --git a/gnovm/tests/files/const23.gno b/gnovm/tests/files/const23.gno new file mode 100644 index 00000000000..f445c3e8eb2 --- /dev/null +++ b/gnovm/tests/files/const23.gno @@ -0,0 +1,11 @@ +package main + +import "fmt" + +func main() { + const t []string = []string{} + fmt.Println(t) +} + +// Error: +// main/files/const23.gno:6:8: invalid constant type []string diff --git a/gnovm/tests/files/const24.gno b/gnovm/tests/files/const24.gno new file mode 100644 index 00000000000..d82c50c86aa --- /dev/null +++ b/gnovm/tests/files/const24.gno @@ -0,0 +1,82 @@ +package main + +import ( + "fmt" + "time" +) + +func main() { + const a int = 1_000_000 + const b byte = byte(1) + const c float64 = 1_000_000.000 + const d string = "Hello, World!" + const e rune = 'a' + const g bool = true + const h uint = 1_000 + const i int8 = 1 + const j int16 = 1 + const k int32 = 1 + const l int64 = 1 + const m uint8 = 1 + const n uint16 = 1 + const o uint32 = 1 + const p uint64 = 1 + const r float32 = 1_000_000.000 + const s = r + const t = len("s") + const u = 1 + len("s") + 3 + ars := [10]string{} + const v = len(ars) + const w = cap(ars) + const x = time.Second + const y = +len("ay") + + fmt.Println(a) + fmt.Println(b) + fmt.Println(c) + fmt.Println(d) + fmt.Println(e) + fmt.Println(g) + fmt.Println(h) + fmt.Println(i) + fmt.Println(j) + fmt.Println(k) + fmt.Println(l) + fmt.Println(m) + fmt.Println(n) + fmt.Println(o) + fmt.Println(p) + fmt.Println(r) + fmt.Println(s) + fmt.Println(t) + fmt.Println(u) + fmt.Println(v) + fmt.Println(w) + println(x) + fmt.Println(y) +} + +// Output: +// 1000000 +// 1 +// 1e+06 +// Hello, World! +// 97 +// true +// 1000 +// 1 +// 1 +// 1 +// 1 +// 1 +// 1 +// 1 +// 1 +// 1e+06 +// 1e+06 +// 1 +// 5 +// 10 +// 10 +// 1s +// 2 diff --git a/gnovm/tests/files/const25.gno b/gnovm/tests/files/const25.gno new file mode 100644 index 00000000000..64e0358bdef --- /dev/null +++ b/gnovm/tests/files/const25.gno @@ -0,0 +1,11 @@ +package main + +import "fmt" + +func main() { + const t = []string{"1"} + fmt.Println(t) +} + +// Error: +// main/files/const25.gno:6:8: [](const-type string){(const ("1" string))} (variable of type []string) is not constant diff --git a/gnovm/tests/files/const26.gno b/gnovm/tests/files/const26.gno new file mode 100644 index 00000000000..a1533e98c57 --- /dev/null +++ b/gnovm/tests/files/const26.gno @@ -0,0 +1,15 @@ +package main + +import "fmt" + +func v() string { + return "" +} + +func main() { + const t = v() + fmt.Println(t) +} + +// Error: +// main/files/const26.gno:10:8: v() (value of type string) is not constant diff --git a/gnovm/tests/files/const27.gno b/gnovm/tests/files/const27.gno new file mode 100644 index 00000000000..4be731e16a7 --- /dev/null +++ b/gnovm/tests/files/const27.gno @@ -0,0 +1,16 @@ +package main + +import "fmt" + +func v() string { + return "" +} + +func main() { + var i interface{} = 1 + const t, ok = i.(int) + fmt.Println(t, ok) +} + +// Error: +// main/files/const27.gno:11:8: i.((const-type int)) (comma, ok expression of type (const-type int)) is not constant diff --git a/gnovm/tests/files/const28.gno b/gnovm/tests/files/const28.gno new file mode 100644 index 00000000000..e4762c3205c --- /dev/null +++ b/gnovm/tests/files/const28.gno @@ -0,0 +1,12 @@ +package main + +import "fmt" + +func main() { + var s []string = []string{"1"} + const t, ok = s[0] + fmt.Println(t, ok) +} + +// Error: +// main/files/const28.gno:7:8: s[(const (0 int))] (variable of type string) is not constant diff --git a/gnovm/tests/files/const29.gno b/gnovm/tests/files/const29.gno new file mode 100644 index 00000000000..41d8d0a816d --- /dev/null +++ b/gnovm/tests/files/const29.gno @@ -0,0 +1,12 @@ +package main + +import "fmt" + +func main() { + s := "1" + const t = s + fmt.Println(t) +} + +// Error: +// main/files/const29.gno:7:8: s (variable of type string) is not constant diff --git a/gnovm/tests/files/const30.gno b/gnovm/tests/files/const30.gno new file mode 100644 index 00000000000..4a166013cdc --- /dev/null +++ b/gnovm/tests/files/const30.gno @@ -0,0 +1,15 @@ +package main + +import "fmt" + +func v() { + return +} + +func main() { + const t = v() + fmt.Println(t) +} + +// Error: +// main/files/const30.gno:10:8: v (no value) used as value diff --git a/gnovm/tests/files/const31.gno b/gnovm/tests/files/const31.gno new file mode 100644 index 00000000000..e37577789e6 --- /dev/null +++ b/gnovm/tests/files/const31.gno @@ -0,0 +1,15 @@ +package main + +import "fmt" + +func v() (string, string) { + return "", "" +} + +func main() { + const t, v = v() + fmt.Println(t) +} + +// Error: +// main/files/const31.gno:10:8: (const (v func()( string, string)))() (variable of type (string,string)) is not constant diff --git a/gnovm/tests/files/const32.gno b/gnovm/tests/files/const32.gno new file mode 100644 index 00000000000..83d3ae5e73c --- /dev/null +++ b/gnovm/tests/files/const32.gno @@ -0,0 +1,11 @@ +package main + +import "fmt" + +func main() { + const t = 1 + 2 + len([]string{}) + fmt.Println(t) +} + +// Error: +// main/files/const32.gno:6:8: [](const-type string){} (variable of type []string) is not constant diff --git a/gnovm/tests/files/const33.gno b/gnovm/tests/files/const33.gno new file mode 100644 index 00000000000..42652d3b458 --- /dev/null +++ b/gnovm/tests/files/const33.gno @@ -0,0 +1,12 @@ +package main + +var x = 1 +var y = 1 + +const b = x == y + +func main() { + println("ok") +} +// Error: +// main/files/const33.gno:6:7: x (variable of type int) is not constant diff --git a/gnovm/tests/files/const34.gno b/gnovm/tests/files/const34.gno new file mode 100644 index 00000000000..9c79cf86b88 --- /dev/null +++ b/gnovm/tests/files/const34.gno @@ -0,0 +1,10 @@ +package main + +const a = func() { println("hey") } + +func main() { + println("ok") +} + +// Error: +// main/files/const34.gno:3:7: func func(){ (const (println func(xs ...interface{})()))((const ("hey" string))) } (variable of type func()()) is not constant diff --git a/gnovm/tests/files/const35.gno b/gnovm/tests/files/const35.gno new file mode 100644 index 00000000000..e85d68eb8db --- /dev/null +++ b/gnovm/tests/files/const35.gno @@ -0,0 +1,12 @@ +package main + +var x = 1 + +const ff = +(1 << x) + +func main() { + println("ok") +} + +// Error: +// main/files/const35.gno:5:7: x (variable of type int) is not constant diff --git a/gnovm/tests/files/const36.gno b/gnovm/tests/files/const36.gno new file mode 100644 index 00000000000..8a677a35be9 --- /dev/null +++ b/gnovm/tests/files/const36.gno @@ -0,0 +1,13 @@ +package main + +type s struct { + x int +} + +func main() { + s := s{1} + const v = s.x +} + +// Error: +// main/files/const36.gno:9:8: s.x (variable of type int) is not constant diff --git a/gnovm/tests/files/const37_native.gno b/gnovm/tests/files/const37_native.gno new file mode 100644 index 00000000000..6a164328a3b --- /dev/null +++ b/gnovm/tests/files/const37_native.gno @@ -0,0 +1,10 @@ +package main + +import "time" + +func main() { + const v = time.UTC +} + +// Error: +// main/files/const37_native.gno:6:8: time.UTC (variable of type *time.Location) is not constant diff --git a/gnovm/tests/files/const37_stdlibs.gno b/gnovm/tests/files/const37_stdlibs.gno new file mode 100644 index 00000000000..9ec614f2538 --- /dev/null +++ b/gnovm/tests/files/const37_stdlibs.gno @@ -0,0 +1,10 @@ +package main + +import "time" + +func main() { + const v = time.UTC +} + +// Error: +// main/files/const37_stdlibs.gno:6:8: time.UTC (variable of type *time.Location) is not constant \ No newline at end of file diff --git a/gnovm/tests/files/const38.gno b/gnovm/tests/files/const38.gno new file mode 100644 index 00000000000..815e74fa76a --- /dev/null +++ b/gnovm/tests/files/const38.gno @@ -0,0 +1,11 @@ +package main + +import "std" + +func main() { + v := std.Coin{} + const c = v.Denom +} + +// Error: +// main/files/const38.gno:7:8: v.Denom (variable of type string) is not constant diff --git a/gnovm/tests/files/const39.gno b/gnovm/tests/files/const39.gno new file mode 100644 index 00000000000..68ff7dd5630 --- /dev/null +++ b/gnovm/tests/files/const39.gno @@ -0,0 +1,14 @@ +package main + +type T struct { + a int +} + +func (tv T) Mv(a int) int { return 0 } + +func main() { + const t = T.Mv +} + +// Error: +// main/files/const39.gno:10:8: T.Mv (variable of type func(tv main.T,a int)( int)) is not constant diff --git a/gnovm/tests/files/const40.gno b/gnovm/tests/files/const40.gno new file mode 100644 index 00000000000..d1dedc382e6 --- /dev/null +++ b/gnovm/tests/files/const40.gno @@ -0,0 +1,8 @@ +package main + +func main() { + const t = [0]string{} +} + +// Error: +// main/files/const40.gno:4:8: [(const (0 int))](const-type string){} (variable of type [0]string) is not constant diff --git a/gnovm/tests/files/const41.gno b/gnovm/tests/files/const41.gno new file mode 100644 index 00000000000..b4424dcef94 --- /dev/null +++ b/gnovm/tests/files/const41.gno @@ -0,0 +1,8 @@ +package main + +func main() { + const t [0]string = [0]string{} +} + +// Error: +// main/files/const41.gno:4:8: invalid constant type [0]string \ No newline at end of file diff --git a/gnovm/tests/files/const42.gno b/gnovm/tests/files/const42.gno new file mode 100644 index 00000000000..5763a2fc121 --- /dev/null +++ b/gnovm/tests/files/const42.gno @@ -0,0 +1,8 @@ +package main + +func main() { + const t int +} + +// Error: +// main/files/const42.gno:4:8: missing init expr for t \ No newline at end of file diff --git a/gnovm/tests/files/const43.gno b/gnovm/tests/files/const43.gno new file mode 100644 index 00000000000..d929e526579 --- /dev/null +++ b/gnovm/tests/files/const43.gno @@ -0,0 +1,10 @@ +package main + +func main() { + a := [2]int{1, 2} + + const b = a +} + +// Error: +// main/files/const43.gno:6:8: a (variable of type [2]int) is not constant diff --git a/gnovm/tests/files/const44.gno b/gnovm/tests/files/const44.gno new file mode 100644 index 00000000000..69924b8ea9d --- /dev/null +++ b/gnovm/tests/files/const44.gno @@ -0,0 +1,10 @@ +package main + +const a = interface{}(nil) + +func main() { + println("ok") +} + +// Error: +// main/files/const44.gno:3:7: (const (undefined)) (variable of type interface{}) is not constant diff --git a/gnovm/tests/files/const45_a.gno b/gnovm/tests/files/const45_a.gno new file mode 100644 index 00000000000..fef13e2fc68 --- /dev/null +++ b/gnovm/tests/files/const45_a.gno @@ -0,0 +1,14 @@ +package main + +type MyStruct struct { + arr [2]int +} + +const a = len(MyStruct{arr: [2]int{1, 2}}.arr) + +func main() { + println("ok") +} + +// Output: +// ok diff --git a/gnovm/tests/files/const45_b.gno b/gnovm/tests/files/const45_b.gno new file mode 100644 index 00000000000..7da25d3268c --- /dev/null +++ b/gnovm/tests/files/const45_b.gno @@ -0,0 +1,14 @@ +package main + +type MyStruct struct { + arr []int +} + +const a = len(MyStruct{arr: []int{1, 2}}.arr) + +func main() { + println("ok") +} + +// Error: +// main/files/const45_b.gno:7:7: MyStruct{arr: [](const-type int){(const (1 int)), (const (2 int))}}.arr (variable of type []int) is not constant diff --git a/gnovm/tests/files/const46.gno b/gnovm/tests/files/const46.gno new file mode 100644 index 00000000000..4722cba294e --- /dev/null +++ b/gnovm/tests/files/const46.gno @@ -0,0 +1,10 @@ +package main + +const a = len(map[string][2]int{"arr": {1, 2}}["arr"]) + +func main() { + println("ok") +} + +// Output: +// ok diff --git a/gnovm/tests/files/const47.gno b/gnovm/tests/files/const47.gno new file mode 100644 index 00000000000..528ba469562 --- /dev/null +++ b/gnovm/tests/files/const47.gno @@ -0,0 +1,10 @@ +package main + +const a = len(map[string][2]interface{}{"arr": {1, 2}}["arr"]) + +func main() { + println("ok") +} + +// Output: +// ok diff --git a/gnovm/tests/files/const48.gno b/gnovm/tests/files/const48.gno new file mode 100644 index 00000000000..001fd911fa5 --- /dev/null +++ b/gnovm/tests/files/const48.gno @@ -0,0 +1,9 @@ +package main + +func main() { + const a = len(map[string][]int{"arr": {1, 2}}) + println("ok", a) +} + +// Error: +// main/files/const48.gno:4:8: map[(const-type string)] [](const-type int){(const ("arr" string)): (const-type []int){(const (1 int)), (const (2 int))}} (variable of type map[string][]int) is not constant diff --git a/gnovm/tests/files/const49.gno b/gnovm/tests/files/const49.gno new file mode 100644 index 00000000000..4b3f38e4d5e --- /dev/null +++ b/gnovm/tests/files/const49.gno @@ -0,0 +1,16 @@ +package main + +import "io" + +type ( + s [2]int + m s +) + +func main() { + const v = len(m{1, 2}) + println(v) +} + +// Output: +// 2 diff --git a/gnovm/tests/files/const50.gno b/gnovm/tests/files/const50.gno new file mode 100644 index 00000000000..5144173bf74 --- /dev/null +++ b/gnovm/tests/files/const50.gno @@ -0,0 +1,12 @@ +package main + +var x = "a" + +const v = len(x) + +func main() { + println("ok") +} + +// Error: +// main/files/const50.gno:5:7: x (variable of type string) is not constant From bd1d104e7bd88ab8980ad2d91bac65b7eaffc354 Mon Sep 17 00:00:00 2001 From: Ursulovic <97893481+Ursulovic@users.noreply.github.com> Date: Wed, 15 Jan 2025 00:08:43 +0100 Subject: [PATCH 12/20] feat(examples): Ivan's registry, home realm (#3354) Created registry for further development of realms, finalising home realm. --------- Co-authored-by: Ivan Ursulovic Co-authored-by: Ivan Ursulovic Co-authored-by: Morgan --- examples/gno.land/r/ursulovic/home/gno.mod | 1 + examples/gno.land/r/ursulovic/home/home.gno | 159 ++++++++++++++++++ .../gno.land/r/ursulovic/home/home_test.gno | 97 +++++++++++ .../gno.land/r/ursulovic/registry/gno.mod | 1 + .../r/ursulovic/registry/registry.gno | 59 +++++++ 5 files changed, 317 insertions(+) create mode 100644 examples/gno.land/r/ursulovic/home/gno.mod create mode 100644 examples/gno.land/r/ursulovic/home/home.gno create mode 100644 examples/gno.land/r/ursulovic/home/home_test.gno create mode 100644 examples/gno.land/r/ursulovic/registry/gno.mod create mode 100644 examples/gno.land/r/ursulovic/registry/registry.gno diff --git a/examples/gno.land/r/ursulovic/home/gno.mod b/examples/gno.land/r/ursulovic/home/gno.mod new file mode 100644 index 00000000000..78163ab2bb5 --- /dev/null +++ b/examples/gno.land/r/ursulovic/home/gno.mod @@ -0,0 +1 @@ +module gno.land/r/ursulovic/home diff --git a/examples/gno.land/r/ursulovic/home/home.gno b/examples/gno.land/r/ursulovic/home/home.gno new file mode 100644 index 00000000000..c03d8a66868 --- /dev/null +++ b/examples/gno.land/r/ursulovic/home/home.gno @@ -0,0 +1,159 @@ +package home + +import ( + "std" + "strconv" + "strings" + + "gno.land/p/demo/ownable" + "gno.land/p/moul/md" + "gno.land/r/leon/hof" + + "gno.land/r/ursulovic/registry" +) + +var ( + aboutMe string + selectedImage string + Ownable *ownable.Ownable + + githubUrl string + linkedinUrl string + connectUrl string + imageUpdatePrice int64 + + isValidUrl func(string) bool +) + +func init() { + Ownable = ownable.NewWithAddress(registry.MainAddress()) + + aboutMe = "Hi, I'm Ivan Ursulovic, a computer engineering graduate, blockchain enthusiast, and backend developer specializing in ASP.NET. I love learning new things and taking on challenges." + selectedImage = "https://i.ibb.co/W28NPkw/beograd.webp" + + githubUrl = "https://github.com/ursulovic" + linkedinUrl = "https://www.linkedin.com/in/ivan-ursulovic-953310190/" + imageUpdatePrice = 5000000 + isValidUrl = defaultURLValidation + hof.Register() +} + +func Render(s string) string { + var sb strings.Builder + sb.WriteString(renderAboutMe()) + sb.WriteString(renderSelectedImage()) + sb.WriteString(renderContactsUrl()) + return sb.String() +} + +func defaultURLValidation(url string) bool { + const urlPrefix string = "https://i.ibb.co/" + + if !strings.HasPrefix(url, urlPrefix) { + return false + } + + if !(strings.HasSuffix(url, ".jpg") || + strings.HasSuffix(url, ".png") || + strings.HasSuffix(url, ".gif") || + strings.HasSuffix(url, ".webp")) { + return false + } + + urlPath := strings.TrimPrefix(url, "https://i.ibb.co/") + parts := strings.Split(urlPath, "/") + + if len(parts) != 2 || len(parts[0]) == 0 || len(parts[1]) == 0 { + return false + } + + return true +} + +func UpdateSelectedImage(url string) { + if !isValidUrl(url) { + panic("Url is not valid!") + } + + sentCoins := std.GetOrigSend() + + if len(sentCoins) != 1 && sentCoins.AmountOf("ugnot") == imageUpdatePrice { + panic("Please send exactly " + strconv.Itoa(int(imageUpdatePrice)) + " ugnot") + } + + selectedImage = url +} + +func renderSelectedImage() string { + var sb strings.Builder + + sb.WriteString(md.HorizontalRule()) + sb.WriteString("\n") + + sb.WriteString(md.H2("📸 Featured Image")) + sb.WriteString("\n") + + sb.WriteString(md.Image("", selectedImage)) + sb.WriteString("\n") + + sb.WriteString(md.H4("✨ " + md.Link("Change this image for "+strconv.Itoa(int(imageUpdatePrice/1000000))+" GNOT. To update, set a direct image URL from ImgBB.", "https://gno.studio/connect/view/gno.land/r/ursulovic/home?network=portal-loop") + " ✨")) + + return sb.String() +} + +func renderAboutMe() string { + var sb strings.Builder + + sb.WriteString(md.H1("👋 Welcome to Ivan's Homepage!")) + sb.WriteString("\n") + + sb.WriteString(md.H2("👨‍💻 About Me")) + sb.WriteString("\n") + + sb.WriteString(md.Blockquote(aboutMe)) + + return sb.String() +} + +func renderContactsUrl() string { + var sb strings.Builder + + sb.WriteString(md.HorizontalRule()) + sb.WriteString("\n") + + sb.WriteString(md.H2("🔗 Let's Connect")) + sb.WriteString("\n") + + items := []string{ + "🐙 " + md.Link("GitHub", githubUrl), + "💼 " + md.Link("LinkedIn", linkedinUrl), + } + sb.WriteString(md.BulletList(items)) + + return sb.String() +} + +func UpdateGithubUrl(url string) { + Ownable.AssertCallerIsOwner() + githubUrl = url +} + +func UpdateLinkedinUrl(url string) { + Ownable.AssertCallerIsOwner() + linkedinUrl = url +} + +func UpdateAboutMe(text string) { + Ownable.AssertCallerIsOwner() + aboutMe = text +} + +func UpdateImagePrice(newPrice int64) { + Ownable.AssertCallerIsOwner() + imageUpdatePrice = newPrice +} + +func UpdateIsValidUrlFunction(f func(string) bool) { + Ownable.AssertCallerIsOwner() + isValidUrl = f +} diff --git a/examples/gno.land/r/ursulovic/home/home_test.gno b/examples/gno.land/r/ursulovic/home/home_test.gno new file mode 100644 index 00000000000..ff3f763d62a --- /dev/null +++ b/examples/gno.land/r/ursulovic/home/home_test.gno @@ -0,0 +1,97 @@ +package home + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" +) + +func TestUpdateGithubUrl(t *testing.T) { + caller := std.Address("g1d24j8fwnc0w5q427fauyey4gdd30qgu69k6n0x") + std.TestSetOrigCaller(caller) + + newUrl := "https://github.com/example" + + UpdateGithubUrl(newUrl) + + if githubUrl != newUrl { + t.Fatalf("GitHub url not updated properly!") + } +} + +func TestUpdateLinkedinUrl(t *testing.T) { + caller := std.Address("g1d24j8fwnc0w5q427fauyey4gdd30qgu69k6n0x") + std.TestSetOrigCaller(caller) + + newUrl := "https://www.linkedin.com/in/example" + + UpdateGithubUrl(newUrl) + + if githubUrl != newUrl { + t.Fatalf("LinkedIn url not updated properly!") + } +} + +func TestUpdateAboutMe(t *testing.T) { + caller := std.Address("g1d24j8fwnc0w5q427fauyey4gdd30qgu69k6n0x") + std.TestSetOrigCaller(caller) + + newAboutMe := "This is new description!" + + UpdateAboutMe(newAboutMe) + + if aboutMe != newAboutMe { + t.Fatalf("About mew not updated properly!") + } +} + +func TestUpdateSelectedImage(t *testing.T) { + var user = testutils.TestAddress("user") + std.TestSetOrigCaller(user) + + validImageUrl := "https://i.ibb.co/hLtmnX0/beautiful-rain-forest-ang-ka-nature-trail-doi-inthanon-national-park-thailand-36703721.webp" + + coinsSent := std.NewCoins(std.NewCoin("ugnot", 5000000)) // Update to match the price expected by your function + std.TestSetOrigSend(coinsSent, std.NewCoins()) + + UpdateSelectedImage(validImageUrl) + + if selectedImage != validImageUrl { + t.Fatalf("Valid image URL rejected!") + } + + invalidImageUrl := "https://ibb.co/Kb3rQNn" + + defer func() { + if r := recover(); r == nil { + t.Fatalf("Expected panic for invalid image URL, but got no panic") + } + }() + + UpdateSelectedImage(invalidImageUrl) + + invalidCoins := std.NewCoins(std.NewCoin("ugnot", 1000000)) + std.TestSetOrigSend(invalidCoins, std.NewCoins()) + + defer func() { + if r := recover(); r == nil { + t.Fatalf("Expected panic for incorrect coin denomination or amount, but got no panic") + } + }() + + UpdateSelectedImage(validImageUrl) +} + +func TestUpdateImagePrice(t *testing.T) { + caller := std.Address("g1d24j8fwnc0w5q427fauyey4gdd30qgu69k6n0x") + std.TestSetOrigCaller(caller) + + var newImageUpdatePrice int64 = 3000000 + + UpdateImagePrice(newImageUpdatePrice) + + if imageUpdatePrice != newImageUpdatePrice { + t.Fatalf("Image update price not updated properly!") + } +} diff --git a/examples/gno.land/r/ursulovic/registry/gno.mod b/examples/gno.land/r/ursulovic/registry/gno.mod new file mode 100644 index 00000000000..ee1f5d38780 --- /dev/null +++ b/examples/gno.land/r/ursulovic/registry/gno.mod @@ -0,0 +1 @@ +module gno.land/r/ursulovic/registry diff --git a/examples/gno.land/r/ursulovic/registry/registry.gno b/examples/gno.land/r/ursulovic/registry/registry.gno new file mode 100644 index 00000000000..0bbd6c80df5 --- /dev/null +++ b/examples/gno.land/r/ursulovic/registry/registry.gno @@ -0,0 +1,59 @@ +package registry + +import ( + "errors" + "std" +) + +var ( + mainAddress std.Address + backupAddress std.Address + + ErrInvalidAddr = errors.New("Ivan's registry: Invalid address") + ErrUnauthorized = errors.New("Ivan's registry: Unauthorized") +) + +func init() { + mainAddress = "g1d24j8fwnc0w5q427fauyey4gdd30qgu69k6n0x" + backupAddress = "g1mw2xft3eava9kfhqw3fjj3kkf3pkammty0mtv7" +} + +func MainAddress() std.Address { + return mainAddress +} + +func BackupAddress() std.Address { + return backupAddress +} + +func SetMainAddress(addr std.Address) error { + assertAuthorized() + + if !addr.IsValid() { + return ErrInvalidAddr + } + + mainAddress = addr + return nil +} + +func SetBackupAddress(addr std.Address) error { + assertAuthorized() + + if !addr.IsValid() { + return ErrInvalidAddr + } + + backupAddress = addr + return nil +} + +// It will stay here for now, might be useful later +func assertAuthorized() { + caller := std.PrevRealm().Addr() + isAuthorized := caller == mainAddress || caller == backupAddress + + if !isAuthorized { + panic(ErrUnauthorized) + } +} From 90ff3e440446a2604c32d185f0bb63d31c499a51 Mon Sep 17 00:00:00 2001 From: 6h057 <15034695+omarsy@users.noreply.github.com> Date: Wed, 15 Jan 2025 10:19:20 +0100 Subject: [PATCH 13/20] fix(gnovm): save object when refCount changed (#2992) This PR a fix in the gnovm to ensure that objects are saved correctly when their reference count changes. closes: #2266 #1543
Contributors' checklist... - [ ] Added new tests, or not needed, or not feasible - [ ] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [ ] Updated the official documentation or not needed - [ ] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [ ] Added references to related issues and PRs - [ ] Provided any useful hints for running manual tests
--- contribs/gnodev/pkg/dev/node_test.go | 2 +- examples/gno.land/p/demo/avl/z_0_filetest.gno | 113 - examples/gno.land/p/demo/avl/z_1_filetest.gno | 161 +- examples/gno.land/p/demo/avl/z_2_filetest.gno | 50 +- .../gno.land/r/demo/boards/z_4_filetest.gno | 19 + .../testdata/addpkg_namespace.txtar | 2 +- .../pkg/integration/testdata/ghverify.txtar | 2 +- .../pkg/integration/testdata/issue_1543.txtar | 41 + .../pkg/integration/testdata/issue_2266.txtar | 42 + .../integration/testdata/simulate_gas.txtar | 4 +- gno.land/pkg/sdk/vm/gas_test.go | 4 +- .../cmd/gno/testdata/test/realm_correct.txtar | 67 +- .../gno/testdata/test/realm_incorrect.txtar | 2 +- gnovm/cmd/gno/testdata/test/realm_sync.txtar | 65 - gnovm/pkg/gnolang/realm.go | 8 +- gnovm/pkg/gnolang/store.go | 4 +- .../more/realm_compositelit_filetest.gno | 146 -- gnovm/tests/files/heap_item_value.gno | 125 - gnovm/tests/files/heap_item_value_init.gno | 168 +- gnovm/tests/files/zrealm0.gno | 65 - gnovm/tests/files/zrealm1.gno | 156 -- gnovm/tests/files/zrealm13.gno | 77 + gnovm/tests/files/zrealm14.gno | 168 ++ gnovm/tests/files/zrealm15.gno | 178 ++ gnovm/tests/files/zrealm16.gno | 115 + gnovm/tests/files/zrealm2.gno | 192 -- gnovm/tests/files/zrealm3.gno | 186 -- gnovm/tests/files/zrealm4.gno | 120 +- gnovm/tests/files/zrealm5.gno | 120 +- gnovm/tests/files/zrealm6.gno | 141 +- gnovm/tests/files/zrealm7.gno | 236 +- gnovm/tests/files/zrealm_avl0.gno | 113 - gnovm/tests/files/zrealm_avl1.gno | 161 +- gnovm/tests/files/zrealm_crossrealm21.gno | 672 +---- gnovm/tests/files/zrealm_crossrealm22.gno | 2176 +---------------- gnovm/tests/files/zrealm_natbind0.gno | 150 -- gnovm/tests/files/zrealm_tests0.gno | 1665 +------------ 37 files changed, 1002 insertions(+), 6714 deletions(-) create mode 100644 gno.land/pkg/integration/testdata/issue_1543.txtar create mode 100644 gno.land/pkg/integration/testdata/issue_2266.txtar create mode 100644 gnovm/tests/files/zrealm13.gno create mode 100644 gnovm/tests/files/zrealm14.gno create mode 100644 gnovm/tests/files/zrealm15.gno create mode 100644 gnovm/tests/files/zrealm16.gno diff --git a/contribs/gnodev/pkg/dev/node_test.go b/contribs/gnodev/pkg/dev/node_test.go index 4a4acc232b9..38fab0a3360 100644 --- a/contribs/gnodev/pkg/dev/node_test.go +++ b/contribs/gnodev/pkg/dev/node_test.go @@ -438,7 +438,7 @@ func testingCallRealm(t *testing.T, node *Node, msgs ...vm.MsgCall) (*core_types txcfg := gnoclient.BaseTxCfg{ GasFee: ugnot.ValueString(1000000), // Gas fee - GasWanted: 2_000_000, // Gas wanted + GasWanted: 3_000_000, // Gas wanted } // Set Caller in the msgs diff --git a/examples/gno.land/p/demo/avl/z_0_filetest.gno b/examples/gno.land/p/demo/avl/z_0_filetest.gno index 2dce5e7f1ac..1db1adebd3e 100644 --- a/examples/gno.land/p/demo/avl/z_0_filetest.gno +++ b/examples/gno.land/p/demo/avl/z_0_filetest.gno @@ -215,116 +215,3 @@ func main() { // } // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ -// "Blank": {}, -// "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", -// "IsEscaped": true, -// "ModTime": "5", -// "RefCount": "2" -// }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "ae86874f9b47fa5e64c30b3e92e9d07f2ec967a4", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "z_0.gno", -// "IsMethod": false, -// "Name": "init.1", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "z_0.gno", -// "Line": "10", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "z_0.gno", -// "IsMethod": false, -// "Name": "main", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "z_0.gno", -// "Line": "15", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// } -// ] -// } diff --git a/examples/gno.land/p/demo/avl/z_1_filetest.gno b/examples/gno.land/p/demo/avl/z_1_filetest.gno index 97ca5ed2135..572c49333bc 100644 --- a/examples/gno.land/p/demo/avl/z_1_filetest.gno +++ b/examples/gno.land/p/demo/avl/z_1_filetest.gno @@ -24,6 +24,44 @@ func main() { // Realm: // switchrealm["gno.land/r/test"] +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:6]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "ModTime": "11", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "1375f6f96a1a3f298347dc8fc0065afa36cb7f0f", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" +// } +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:8]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "ModTime": "13", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "b28057ab7be6383785c0a5503e8a531bdbc21851", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9" +// } +// } +// } // c[a8ada09dee16d791fd406d629fe29bb0ed084a30:15]={ // "Fields": [ // { @@ -143,7 +181,7 @@ func main() { // "@type": "/gno.PointerValue", // "Base": { // "@type": "/gno.RefValue", -// "Hash": "2f3adc5d0f2a3fe0331cfa93572a7abdde14c9aa", +// "Hash": "cafae89e4d4aaaefe7fdf0691084508d4274a981", // "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8" // }, // "Index": "0", @@ -191,7 +229,7 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "fe20a19f956511f274dc77854e9e5468387260f4", +// "Hash": "b2e446f490656c19a83c43055de29c96e92a1549", // "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:13" // } // } @@ -235,7 +273,7 @@ func main() { // "@type": "/gno.PointerValue", // "Base": { // "@type": "/gno.RefValue", -// "Hash": "c89a71bdf045e8bde2059dc9d33839f916e02e5d", +// "Hash": "4e56eeb96eb1d9b27cf603140cd03a1622b6358b", // "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" // }, // "Index": "0", @@ -254,7 +292,7 @@ func main() { // "@type": "/gno.PointerValue", // "Base": { // "@type": "/gno.RefValue", -// "Hash": "90fa67f8c47db4b9b2a60425dff08d5a3385100f", +// "Hash": "7b61530859954d1d14b2f696c91c5f37d39c21e7", // "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:12" // }, // "Index": "0", @@ -283,123 +321,10 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "83e42caaf53070dd95b5f859053eb51ed900bbda", +// "Hash": "fedc6d430b38c985dc6a985b2fcaee97e88ba6da", // "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:11" // } // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ -// "Blank": {}, -// "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", -// "IsEscaped": true, -// "ModTime": "9", -// "RefCount": "2" -// }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "1faa9fa4ba1935121a6d3f0a623772e9d4499b0a", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "z_1.gno", -// "IsMethod": false, -// "Name": "init.1", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "z_1.gno", -// "Line": "10", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "z_1.gno", -// "IsMethod": false, -// "Name": "main", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "z_1.gno", -// "Line": "15", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// } -// ] -// } // d[a8ada09dee16d791fd406d629fe29bb0ed084a30:4] // d[a8ada09dee16d791fd406d629fe29bb0ed084a30:5] diff --git a/examples/gno.land/p/demo/avl/z_2_filetest.gno b/examples/gno.land/p/demo/avl/z_2_filetest.gno index 43067c31e8f..c45088075d6 100644 --- a/examples/gno.land/p/demo/avl/z_2_filetest.gno +++ b/examples/gno.land/p/demo/avl/z_2_filetest.gno @@ -23,6 +23,44 @@ func main() { // Realm: // switchrealm["gno.land/r/test"] +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:7]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "ModTime": "12", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "ba7550123807b8da857e38b72f66204b1ec582a2", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8" +// } +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:9]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", +// "ModTime": "14", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "3cb8485664c356fcb5c88dfb96b7455133a6b022", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10" +// } +// } +// } // c[a8ada09dee16d791fd406d629fe29bb0ed084a30:16]={ // "Fields": [ // { @@ -142,7 +180,7 @@ func main() { // "@type": "/gno.PointerValue", // "Base": { // "@type": "/gno.RefValue", -// "Hash": "849a50d6c78d65742752e3c89ad8dd556e2e63cb", +// "Hash": "db39c9c0a60e0d5b30dbaf9be6150d3fec16aa4b", // "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9" // }, // "Index": "0", @@ -190,7 +228,7 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "a1160b0060ad752dbfe5fe436f7734bb19136150", +// "Hash": "2e9127534f91b385426d76e8e164f50f635cc1de", // "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:14" // } // } @@ -234,7 +272,7 @@ func main() { // "@type": "/gno.PointerValue", // "Base": { // "@type": "/gno.RefValue", -// "Hash": "fd95e08763159ac529e26986d652e752e78b6325", +// "Hash": "43e03b0c877b40c34e12bc2b15560e8ecd42ae9d", // "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" // }, // "Index": "0", @@ -253,7 +291,7 @@ func main() { // "@type": "/gno.PointerValue", // "Base": { // "@type": "/gno.RefValue", -// "Hash": "3ecdcf148fe2f9e97b72a3bedf303b2ba56d4f4b", +// "Hash": "4b123e2424d900a427f9dee88a70ce61f3cdcf5b", // "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:13" // }, // "Index": "0", @@ -282,7 +320,7 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "63126557dba88f8556f7a0ccbbfc1d218ae7a302", +// "Hash": "76d9227e755efd6674d8fa34e12decb7a9855488", // "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:12" // } // } @@ -301,7 +339,7 @@ func main() { // "@type": "/gno.PointerValue", // "Base": { // "@type": "/gno.RefValue", -// "Hash": "d31c7e797793e03ffe0bbcb72f963264f8300d22", +// "Hash": "ff46b4dd63457c3fd59801e725f65af524ec829d", // "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:11" // }, // "Index": "0", diff --git a/examples/gno.land/r/demo/boards/z_4_filetest.gno b/examples/gno.land/r/demo/boards/z_4_filetest.gno index c6cf6397b3a..b781e94e4db 100644 --- a/examples/gno.land/r/demo/boards/z_4_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_4_filetest.gno @@ -885,6 +885,25 @@ func main() { // "RefCount": "1" // } // } +// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:84]={ +// "ObjectInfo": { +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:84", +// "IsEscaped": true, +// "ModTime": "127", +// "RefCount": "6" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/boards.Board" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "a88a9b837af217656ee27084309f7cd02cd94cb3", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:85" +// } +// } +// } // switchrealm["gno.land/r/demo/boards"] // switchrealm["gno.land/r/demo/users"] // switchrealm["gno.land/r/demo/users"] diff --git a/gno.land/pkg/integration/testdata/addpkg_namespace.txtar b/gno.land/pkg/integration/testdata/addpkg_namespace.txtar index f529c176f36..2cfd00acda4 100644 --- a/gno.land/pkg/integration/testdata/addpkg_namespace.txtar +++ b/gno.land/pkg/integration/testdata/addpkg_namespace.txtar @@ -73,7 +73,7 @@ stdout 'OK!' # Test gui publishing on guiland/one # gui addpkg -> gno.land/r/guiland/one -gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/guiland/one -gas-fee 1000000ugnot -gas-wanted 1700000 -broadcast -chainid=tendermint_test gui +gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/guiland/one -gas-fee 1000000ugnot -gas-wanted 1800000 -broadcast -chainid=tendermint_test gui stdout 'OK!' # Test admin publishing on guiland/two diff --git a/gno.land/pkg/integration/testdata/ghverify.txtar b/gno.land/pkg/integration/testdata/ghverify.txtar index b53849e85b5..0f2d21f6bd5 100644 --- a/gno.land/pkg/integration/testdata/ghverify.txtar +++ b/gno.land/pkg/integration/testdata/ghverify.txtar @@ -25,7 +25,7 @@ stdout "" stderr 'invalid ingest id: a' # the agent publishes their response to the task and the verification is complete -gnokey maketx call -pkgpath gno.land/r/gnoland/ghverify -func GnorkleEntrypoint -args 'ingest,g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5,OK' -gas-fee 1000000ugnot -gas-wanted 5000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/gnoland/ghverify -func GnorkleEntrypoint -args 'ingest,g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5,OK' -gas-fee 1000000ugnot -gas-wanted 6000000 -broadcast -chainid=tendermint_test test1 stdout OK! # get verified github handle by gno address diff --git a/gno.land/pkg/integration/testdata/issue_1543.txtar b/gno.land/pkg/integration/testdata/issue_1543.txtar new file mode 100644 index 00000000000..388f126fcda --- /dev/null +++ b/gno.land/pkg/integration/testdata/issue_1543.txtar @@ -0,0 +1,41 @@ +# test issue + +loadpkg gno.land/r/demo/realm $WORK + +# start a new node +gnoland start + + +gnokey maketx call -pkgpath gno.land/r/demo/realm --func Fill --args 0 --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/realm --func UnFill --args 0 --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/realm --func Fill --args 0 --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast -chainid=tendermint_test test1 + + +-- realm.gno -- +package main + +type A struct { + A string +} +type B struct { + A *A + B string +} + +var ( + a = &A{A: "here"} + b [2]*B +) + +func Fill(i int) { + c := B{ + A: a, + B: "", + } + b[i] = &c +} + +func UnFill(i int) { + b[i] = nil +} + diff --git a/gno.land/pkg/integration/testdata/issue_2266.txtar b/gno.land/pkg/integration/testdata/issue_2266.txtar new file mode 100644 index 00000000000..046f57802e3 --- /dev/null +++ b/gno.land/pkg/integration/testdata/issue_2266.txtar @@ -0,0 +1,42 @@ +# test issue + +loadpkg gno.land/r/demo/realm $WORK + +# start a new node +gnoland start + + +gnokey maketx call -pkgpath gno.land/r/demo/realm --func Fill --args 0 --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/realm --func Fill --args 1 --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/realm --func UnFill --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast -chainid=tendermint_test test1 + + +-- realm.gno -- +package main + +type A struct { + A string +} +type B struct { + A *A + B string +} + +var ( + a = &A{A: "here"} + b [2]*B +) + +func Fill(i int) { + c := B{ + A: a, + B: "", + } + b[i] = &c +} + +func UnFill() { + b[0] = nil + b[1] = nil +} + diff --git a/gno.land/pkg/integration/testdata/simulate_gas.txtar b/gno.land/pkg/integration/testdata/simulate_gas.txtar index 57be82b75ff..0dcb9ba424b 100644 --- a/gno.land/pkg/integration/testdata/simulate_gas.txtar +++ b/gno.land/pkg/integration/testdata/simulate_gas.txtar @@ -6,11 +6,11 @@ gnoland start # simulate only gnokey maketx call -pkgpath gno.land/r/simulate -func Hello -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test -simulate only test1 -stdout 'GAS USED: 99339' +stdout 'GAS USED: 99371' # simulate skip gnokey maketx call -pkgpath gno.land/r/simulate -func Hello -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test -simulate skip test1 -stdout 'GAS USED: 99339' # same as simulate only +stdout 'GAS USED: 99371' # same as simulate only -- package/package.gno -- diff --git a/gno.land/pkg/sdk/vm/gas_test.go b/gno.land/pkg/sdk/vm/gas_test.go index 276aa9db0b0..acde3d315c6 100644 --- a/gno.land/pkg/sdk/vm/gas_test.go +++ b/gno.land/pkg/sdk/vm/gas_test.go @@ -74,8 +74,8 @@ func TestAddPkgDeliverTx(t *testing.T) { assert.True(t, res.IsOK()) - // NOTE: let's try to keep this bellow 100_000 :) - assert.Equal(t, int64(135365), gasDeliver) + // NOTE: let's try to keep this bellow 150_000 :) + assert.Equal(t, int64(143845), gasDeliver) } // Enough gas for a failed transaction. diff --git a/gnovm/cmd/gno/testdata/test/realm_correct.txtar b/gnovm/cmd/gno/testdata/test/realm_correct.txtar index ced183bec67..ae1212133fd 100644 --- a/gnovm/cmd/gno/testdata/test/realm_correct.txtar +++ b/gnovm/cmd/gno/testdata/test/realm_correct.txtar @@ -18,69 +18,4 @@ func main() { } // Realm: -// switchrealm["gno.land/r/xx"] -// u[aea84df38908f9569d0f552575606e6e6e7e22dd:2]={ -// "Blank": {}, -// "ObjectInfo": { -// "ID": "aea84df38908f9569d0f552575606e6e6e7e22dd:2", -// "IsEscaped": true, -// "ModTime": "3", -// "RefCount": "2" -// }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/xx" -// } -// }, -// "Values": [ -// { -// "N": "AQAAAAAAAAA=", -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "aea84df38908f9569d0f552575606e6e6e7e22dd:3" -// }, -// "FileName": "x.gno", -// "IsMethod": false, -// "Name": "main", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/xx", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "x.gno", -// "Line": "6", -// "PkgPath": "gno.land/r/xx" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// } -// ] -// } +// switchrealm["gno.land/r/xx"] \ No newline at end of file diff --git a/gnovm/cmd/gno/testdata/test/realm_incorrect.txtar b/gnovm/cmd/gno/testdata/test/realm_incorrect.txtar index 234d0f81e77..84f4e3438ee 100644 --- a/gnovm/cmd/gno/testdata/test/realm_incorrect.txtar +++ b/gnovm/cmd/gno/testdata/test/realm_incorrect.txtar @@ -7,7 +7,7 @@ stderr '=== RUN file/x_filetest.gno' stderr 'Realm diff:' stderr '--- Expected' stderr '\+\+\+ Actual' -stderr '@@ -1,2 \+1,67 @@' +stderr '@@ -1,2 \+1,2 @@' stderr '-xxx' stderr '\+switchrealm\["gno.land/r/xx"\]' stderr 'x_filetest.gno failed' diff --git a/gnovm/cmd/gno/testdata/test/realm_sync.txtar b/gnovm/cmd/gno/testdata/test/realm_sync.txtar index c93e6d86e8f..65a930b2f03 100644 --- a/gnovm/cmd/gno/testdata/test/realm_sync.txtar +++ b/gnovm/cmd/gno/testdata/test/realm_sync.txtar @@ -33,68 +33,3 @@ func main() { // Realm: // switchrealm["gno.land/r/xx"] -// u[aea84df38908f9569d0f552575606e6e6e7e22dd:2]={ -// "Blank": {}, -// "ObjectInfo": { -// "ID": "aea84df38908f9569d0f552575606e6e6e7e22dd:2", -// "IsEscaped": true, -// "ModTime": "3", -// "RefCount": "2" -// }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/xx" -// } -// }, -// "Values": [ -// { -// "N": "AQAAAAAAAAA=", -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "aea84df38908f9569d0f552575606e6e6e7e22dd:3" -// }, -// "FileName": "x.gno", -// "IsMethod": false, -// "Name": "main", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/xx", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "x.gno", -// "Line": "6", -// "PkgPath": "gno.land/r/xx" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// } -// ] -// } diff --git a/gnovm/pkg/gnolang/realm.go b/gnovm/pkg/gnolang/realm.go index 04de760037a..509fcd67a60 100644 --- a/gnovm/pkg/gnolang/realm.go +++ b/gnovm/pkg/gnolang/realm.go @@ -192,6 +192,9 @@ func (rlm *Realm) DidUpdate(po, xo, co Object) { if co != nil { co.IncRefCount() if co.GetRefCount() > 1 { + if co.GetIsReal() { + rlm.MarkDirty(co) + } if co.GetIsEscaped() { // already escaped } else { @@ -211,6 +214,8 @@ func (rlm *Realm) DidUpdate(po, xo, co Object) { if xo.GetIsReal() { rlm.MarkNewDeleted(xo) } + } else if xo.GetIsReal() { + rlm.MarkDirty(xo) } } } @@ -464,6 +469,7 @@ func (rlm *Realm) incRefCreatedDescendants(store Store, oo Object) { child.SetIsNewReal(true) } } else if rc > 1 { + rlm.MarkDirty(child) if child.GetIsEscaped() { // already escaped, do nothing. } else { @@ -537,7 +543,7 @@ func (rlm *Realm) decRefDeletedDescendants(store Store, oo Object) { if rc == 0 { rlm.decRefDeletedDescendants(store, child) } else if rc > 0 { - // do nothing + rlm.MarkDirty(child) } else { panic("deleted descendants should not have a reference count of less than zero") } diff --git a/gnovm/pkg/gnolang/store.go b/gnovm/pkg/gnolang/store.go index bc56a7c6313..3a70d07381b 100644 --- a/gnovm/pkg/gnolang/store.go +++ b/gnovm/pkg/gnolang/store.go @@ -519,7 +519,7 @@ func (ds *defaultStore) SetObject(oo Object) { } ds.cacheObjects[oid] = oo // make store op log entry - if ds.opslog != nil { + if _, ok := oo.(*Block); !ok && ds.opslog != nil { var op StoreOpType if oo.GetIsNewReal() { op = StoreOpNew @@ -560,7 +560,7 @@ func (ds *defaultStore) DelObject(oo Object) { ds.baseStore.Delete([]byte(key)) } // make realm op log entry - if ds.opslog != nil { + if _, ok := oo.(*Block); !ok && ds.opslog != nil { ds.opslog = append(ds.opslog, StoreOp{Type: StoreOpDel, Object: oo}) } diff --git a/gnovm/tests/files/assign_unnamed_type/more/realm_compositelit_filetest.gno b/gnovm/tests/files/assign_unnamed_type/more/realm_compositelit_filetest.gno index 45f83bade5f..8514c2676aa 100644 --- a/gnovm/tests/files/assign_unnamed_type/more/realm_compositelit_filetest.gno +++ b/gnovm/tests/files/assign_unnamed_type/more/realm_compositelit_filetest.gno @@ -89,149 +89,3 @@ func main() { // } // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ -// "Blank": {}, -// "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", -// "IsEscaped": true, -// "ModTime": "3", -// "RefCount": "2" -// }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.PrimitiveType", -// "value": "2048" -// }, -// "Methods": [], -// "Name": "word", -// "PkgPath": "gno.land/r/test" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.SliceType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.word" -// }, -// "Vrd": false -// }, -// "Methods": [], -// "Name": "nat", -// "PkgPath": "gno.land/r/test" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.Int" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "3c89d875f7d6daa94113aa4c7e03432ba56202c2", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.StructType", -// "Fields": [ -// { -// "Embedded": false, -// "Name": "abs", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.nat" -// } -// } -// ], -// "PkgPath": "gno.land/r/test" -// }, -// "Methods": [], -// "Name": "Int", -// "PkgPath": "gno.land/r/test" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/assign_unnamed_type/more/realm_compositelit.gno", -// "IsMethod": false, -// "Name": "main", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/assign_unnamed_type/more/realm_compositelit.gno", -// "Line": "16", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// } -// ] -// } diff --git a/gnovm/tests/files/heap_item_value.gno b/gnovm/tests/files/heap_item_value.gno index 80bf702bec2..7a728d2c6f9 100644 --- a/gnovm/tests/files/heap_item_value.gno +++ b/gnovm/tests/files/heap_item_value.gno @@ -51,128 +51,3 @@ func main() { // } // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ -// "Blank": {}, -// "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", -// "IsEscaped": true, -// "ModTime": "3", -// "RefCount": "2" -// }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.StructType", -// "Fields": [ -// { -// "Embedded": false, -// "Name": "A", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// } -// ], -// "PkgPath": "gno.land/r/test" -// }, -// "Methods": [], -// "Name": "S", -// "PkgPath": "gno.land/r/test" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.S" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.S" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/heap_item_value.gno", -// "IsMethod": false, -// "Name": "main", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/heap_item_value.gno", -// "Line": "10", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// } -// ] -// } diff --git a/gnovm/tests/files/heap_item_value_init.gno b/gnovm/tests/files/heap_item_value_init.gno index 2722cce8675..6be9caed1f4 100644 --- a/gnovm/tests/files/heap_item_value_init.gno +++ b/gnovm/tests/files/heap_item_value_init.gno @@ -19,167 +19,23 @@ func main() { // Realm: // switchrealm["gno.land/r/test"] -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ -// "Blank": {}, +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", // "IsEscaped": true, // "ModTime": "6", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", // "RefCount": "2" // }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.StructType", -// "Fields": [ -// { -// "Embedded": false, -// "Name": "A", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// } -// } -// ], -// "PkgPath": "gno.land/r/test" -// }, -// "Methods": [], -// "Name": "S", -// "PkgPath": "gno.land/r/test" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.S" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.S" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/heap_item_value_init.gno", -// "IsMethod": false, -// "Name": "init.3", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/heap_item_value_init.gno", -// "Line": "10", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.S" // }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/heap_item_value_init.gno", -// "IsMethod": false, -// "Name": "main", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/heap_item_value_init.gno", -// "Line": "16", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "a05e5e1e2d2a27d94408a9325a58068e60b504df", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" // } -// ] +// } // } diff --git a/gnovm/tests/files/zrealm0.gno b/gnovm/tests/files/zrealm0.gno index 5685da2154b..d3bf0e79223 100644 --- a/gnovm/tests/files/zrealm0.gno +++ b/gnovm/tests/files/zrealm0.gno @@ -20,68 +20,3 @@ func main() { // Realm: // switchrealm["gno.land/r/test"] -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ -// "Blank": {}, -// "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", -// "IsEscaped": true, -// "ModTime": "3", -// "RefCount": "2" -// }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Values": [ -// { -// "N": "AQAAAAAAAAA=", -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm0.gno", -// "IsMethod": false, -// "Name": "main", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm0.gno", -// "Line": "6", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// } -// ] -// } diff --git a/gnovm/tests/files/zrealm1.gno b/gnovm/tests/files/zrealm1.gno index bb44a93bad4..eef42cc9378 100644 --- a/gnovm/tests/files/zrealm1.gno +++ b/gnovm/tests/files/zrealm1.gno @@ -47,159 +47,3 @@ func main() { // "RefCount": "1" // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ -// "Blank": {}, -// "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", -// "IsEscaped": true, -// "ModTime": "3", -// "RefCount": "2" -// }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.InnerNode" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "ae4e9e2d205cc0081d4ee249e1d188ebe270b220", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.InterfaceType", -// "Generic": "", -// "Methods": [], -// "PkgPath": "gno.land/r/test" -// }, -// "Methods": [], -// "Name": "Node", -// "PkgPath": "gno.land/r/test" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.InterfaceType", -// "Generic": "", -// "Methods": [], -// "PkgPath": "gno.land/r/test" -// }, -// "Methods": [], -// "Name": "Key", -// "PkgPath": "gno.land/r/test" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.StructType", -// "Fields": [ -// { -// "Embedded": false, -// "Name": "Key", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.Key" -// } -// }, -// { -// "Embedded": false, -// "Name": "Left", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.Node" -// } -// }, -// { -// "Embedded": false, -// "Name": "Right", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.Node" -// } -// } -// ], -// "PkgPath": "gno.land/r/test" -// }, -// "Methods": [], -// "Name": "InnerNode", -// "PkgPath": "gno.land/r/test" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm1.gno", -// "IsMethod": false, -// "Name": "main", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm1.gno", -// "Line": "17", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// } -// ] -// } diff --git a/gnovm/tests/files/zrealm13.gno b/gnovm/tests/files/zrealm13.gno new file mode 100644 index 00000000000..f43f440f731 --- /dev/null +++ b/gnovm/tests/files/zrealm13.gno @@ -0,0 +1,77 @@ +// PKGPATH: gno.land/r/test +package test + +var ( + a = &A{A: "here"} + b [2]*B +) + + +type A struct { + A string +} +type B struct { + A *A + B string +} + +func init() { + c := B{ + A: a, + B: "c", + } + b[0] = &c + + d := B{ + A: a, + B: "d", + } + b[1] = &d +} + +func main() { + b[0] = nil +} + + +// Realm: +// switchrealm["gno.land/r/test"] +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={ +// "Data": null, +// "List": [ +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.B" +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.B" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" +// }, +// "Index": "1", +// "TV": null +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "ModTime": "9", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "RefCount": "1" +// } +// } diff --git a/gnovm/tests/files/zrealm14.gno b/gnovm/tests/files/zrealm14.gno new file mode 100644 index 00000000000..ffffa4883fd --- /dev/null +++ b/gnovm/tests/files/zrealm14.gno @@ -0,0 +1,168 @@ +// PKGPATH: gno.land/r/test +package test + +var ( + a = &A{A: "here"} + b [2]*B +) + + +type A struct { + A string +} +type B struct { + A *A + B string +} + +func init() { + c := B{ + A: a, + B: "c", + } + b[0] = &c + + d := B{ + A: a, + B: "d", + } + b[1] = &d +} + +func main() { + b[0] = nil + b[1] = nil +} + +// Realm: +// switchrealm["gno.land/r/test"] +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={ +// "Data": null, +// "List": [ +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.B" +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.B" +// } +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "ModTime": "9", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "RefCount": "1" +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:8]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.A" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" +// }, +// "Index": "0", +// "TV": null +// } +// }, +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "c" +// } +// } +// ], +// "ObjectInfo": { +// "Hash": "c22ccb7832b422c83fec9943b751cb134fcbed0b", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "RefCount": "0" +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:9]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.A" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" +// }, +// "Index": "0", +// "TV": null +// } +// }, +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "d" +// } +// } +// ], +// "ObjectInfo": { +// "Hash": "86c916fd78da57d354cb38019923bf64c1a3471c", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "RefCount": "0" +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:3]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3", +// "IsEscaped": true, +// "ModTime": "9", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.A" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "95dca2f5b12899b6367402ecdac04c7ca59a03d9", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" +// } +// } +// } +// d[a8ada09dee16d791fd406d629fe29bb0ed084a30:8] +// d[a8ada09dee16d791fd406d629fe29bb0ed084a30:9] diff --git a/gnovm/tests/files/zrealm15.gno b/gnovm/tests/files/zrealm15.gno new file mode 100644 index 00000000000..4ca6ef3b03d --- /dev/null +++ b/gnovm/tests/files/zrealm15.gno @@ -0,0 +1,178 @@ +// PKGPATH: gno.land/r/test +package test + +var ( + a = &A{} + b [2]*B + s *S +) + +type S struct { + S string +} +type A struct { + A *S +} +type B struct { + A *A + B *S +} + +func init() { + s = &S{ + S: "c", + } + c := B{ + A: a, + B: s, + } + b[0] = &c + b[1] = &c + a.A = s +} + +func main() { + b[0] = nil + b[1] = nil + a.A = nil +} + + +// Realm: +// switchrealm["gno.land/r/test"] +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={ +// "Data": null, +// "List": [ +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.B" +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.B" +// } +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "ModTime": "10", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "RefCount": "1" +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:10]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.A" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" +// }, +// "Index": "0", +// "TV": null +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.S" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" +// }, +// "Index": "0", +// "TV": null +// } +// } +// ], +// "ObjectInfo": { +// "Hash": "5bf603ab337f9f40f8b22441562319d67be402b2", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", +// "RefCount": "0" +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.S" +// } +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", +// "ModTime": "10", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3", +// "RefCount": "1" +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:7]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "IsEscaped": true, +// "ModTime": "10", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.S" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "6e4c3c716e28df1d3a25efeb654a7b7a379ce3b0", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8" +// } +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:3]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3", +// "IsEscaped": true, +// "ModTime": "10", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.A" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "e82e410f22425e48d5f6c611160084a4dd50d3d1", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" +// } +// } +// } +// d[a8ada09dee16d791fd406d629fe29bb0ed084a30:10] diff --git a/gnovm/tests/files/zrealm16.gno b/gnovm/tests/files/zrealm16.gno new file mode 100644 index 00000000000..b7bef5028b0 --- /dev/null +++ b/gnovm/tests/files/zrealm16.gno @@ -0,0 +1,115 @@ +// PKGPATH: gno.land/r/test +package test + +var ( + a = &A{A: "here"} + a2 = &A{A: "here"} + b [2]*B +) + +type A struct { + A string +} +type B struct { + A *A + B string +} + +func init() { + c := B{ + A: a, + B: "c", + } + b[0] = &c + + d := B{ + A: a, + B: "d", + } + b[1] = &d +} + +func main() { + b[0].A = a2 +} + +// Realm: +// switchrealm["gno.land/r/test"] +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:10]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.A" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" +// }, +// "Index": "0", +// "TV": null +// } +// }, +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "c" +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10", +// "ModTime": "11", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", +// "RefCount": "1" +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "IsEscaped": true, +// "ModTime": "11", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "RefCount": "2" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.A" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "7c9f93a63419d852f132afaf2245ddcac5e8c3fb", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" +// } +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:3]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3", +// "IsEscaped": true, +// "ModTime": "11", +// "RefCount": "2" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/test.A" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "95dca2f5b12899b6367402ecdac04c7ca59a03d9", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" +// } +// } +// } diff --git a/gnovm/tests/files/zrealm2.gno b/gnovm/tests/files/zrealm2.gno index 7bae02e48bc..57cd8bee349 100644 --- a/gnovm/tests/files/zrealm2.gno +++ b/gnovm/tests/files/zrealm2.gno @@ -50,196 +50,4 @@ func main() { // "RefCount": "1" // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ -// "Blank": {}, -// "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", -// "IsEscaped": true, -// "ModTime": "4", -// "RefCount": "2" -// }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.InnerNode" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "61d4aa77a87c01e07038c6030d6aca299d0fdc1b", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.InterfaceType", -// "Generic": "", -// "Methods": [], -// "PkgPath": "gno.land/r/test" -// }, -// "Methods": [], -// "Name": "Node", -// "PkgPath": "gno.land/r/test" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.InterfaceType", -// "Generic": "", -// "Methods": [], -// "PkgPath": "gno.land/r/test" -// }, -// "Methods": [], -// "Name": "Key", -// "PkgPath": "gno.land/r/test" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.StructType", -// "Fields": [ -// { -// "Embedded": false, -// "Name": "Key", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.Key" -// } -// }, -// { -// "Embedded": false, -// "Name": "Left", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.Node" -// } -// }, -// { -// "Embedded": false, -// "Name": "Right", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.Node" -// } -// } -// ], -// "PkgPath": "gno.land/r/test" -// }, -// "Methods": [], -// "Name": "InnerNode", -// "PkgPath": "gno.land/r/test" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm2.gno", -// "IsMethod": false, -// "Name": "init.4", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm2.gno", -// "Line": "17", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm2.gno", -// "IsMethod": false, -// "Name": "main", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm2.gno", -// "Line": "23", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// } -// ] -// } // d[a8ada09dee16d791fd406d629fe29bb0ed084a30:4] diff --git a/gnovm/tests/files/zrealm3.gno b/gnovm/tests/files/zrealm3.gno index 386fd4fe470..cc9317c32fd 100644 --- a/gnovm/tests/files/zrealm3.gno +++ b/gnovm/tests/files/zrealm3.gno @@ -82,191 +82,5 @@ func main() { // } // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ -// "Blank": {}, -// "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", -// "IsEscaped": true, -// "ModTime": "5", -// "RefCount": "2" -// }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.Node" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "8197b7c5b4f2c7bf9c12b1c614f6b4dc6e7ce8dd", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.InterfaceType", -// "Generic": "", -// "Methods": [], -// "PkgPath": "gno.land/r/test" -// }, -// "Methods": [], -// "Name": "Key", -// "PkgPath": "gno.land/r/test" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.StructType", -// "Fields": [ -// { -// "Embedded": false, -// "Name": "Key", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.Key" -// } -// }, -// { -// "Embedded": false, -// "Name": "Left", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.Node" -// } -// } -// }, -// { -// "Embedded": false, -// "Name": "Right", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/test.Node" -// } -// } -// } -// ], -// "PkgPath": "gno.land/r/test" -// }, -// "Methods": [], -// "Name": "Node", -// "PkgPath": "gno.land/r/test" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm3.gno", -// "IsMethod": false, -// "Name": "init.3", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm3.gno", -// "Line": "14", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm3.gno", -// "IsMethod": false, -// "Name": "main", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm3.gno", -// "Line": "20", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// } -// ] -// } // d[a8ada09dee16d791fd406d629fe29bb0ed084a30:4] // d[a8ada09dee16d791fd406d629fe29bb0ed084a30:5] diff --git a/gnovm/tests/files/zrealm4.gno b/gnovm/tests/files/zrealm4.gno index 8c3857aa37c..f8c5502fcb1 100644 --- a/gnovm/tests/files/zrealm4.gno +++ b/gnovm/tests/files/zrealm4.gno @@ -78,116 +78,22 @@ func main() { // "RefCount": "1" // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ -// "Blank": {}, +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", -// "IsEscaped": true, +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", // "ModTime": "5", -// "RefCount": "2" -// }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/test" -// } +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "RefCount": "1" // }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "7b9d58f40430bbbcbafd47eefb7a6dd342477f71", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" -// }, -// "Index": "0", -// "TV": null -// } +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" // }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm4.gno", -// "IsMethod": false, -// "Name": "init.1", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm4.gno", -// "Line": "11", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm4.gno", -// "IsMethod": false, -// "Name": "main", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm4.gno", -// "Line": "15", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "d57ee28f5030eb8c612b374155fd545675633288", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" // } -// ] +// } // } diff --git a/gnovm/tests/files/zrealm5.gno b/gnovm/tests/files/zrealm5.gno index c1062d51e8d..13d6e4a6b64 100644 --- a/gnovm/tests/files/zrealm5.gno +++ b/gnovm/tests/files/zrealm5.gno @@ -162,116 +162,22 @@ func main() { // "RefCount": "1" // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ -// "Blank": {}, +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", -// "IsEscaped": true, +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", // "ModTime": "5", -// "RefCount": "2" -// }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/test" -// } +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "RefCount": "1" // }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "7b9d58f40430bbbcbafd47eefb7a6dd342477f71", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm5.gno", -// "IsMethod": false, -// "Name": "init.1", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm5.gno", -// "Line": "11", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" // }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm5.gno", -// "IsMethod": false, -// "Name": "main", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm5.gno", -// "Line": "15", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "115779a405a1cae45446397ea897a98a4043cbb2", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" // } -// ] +// } // } diff --git a/gnovm/tests/files/zrealm6.gno b/gnovm/tests/files/zrealm6.gno index 1c50baa2d15..ba162c4d468 100644 --- a/gnovm/tests/files/zrealm6.gno +++ b/gnovm/tests/files/zrealm6.gno @@ -163,6 +163,25 @@ func main() { // "RefCount": "1" // } // } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:6]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "ModTime": "7", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "5d64092f4f064ca58bdeffa32f6a119545b401c8", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" +// } +// } +// } // u[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={ // "Fields": [ // { @@ -213,7 +232,7 @@ func main() { // "@type": "/gno.PointerValue", // "Base": { // "@type": "/gno.RefValue", -// "Hash": "7c63a8fd451cd7c470c1851f1ead037246422ded", +// "Hash": "32593d23afa555fe99d433dbca1130b3843da97a", // "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" // }, // "Index": "0", @@ -228,116 +247,22 @@ func main() { // "RefCount": "1" // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ -// "Blank": {}, +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", -// "IsEscaped": true, +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", // "ModTime": "7", -// "RefCount": "2" -// }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/test" -// } +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "RefCount": "1" // }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "ade9fce2a987ef1924040a1d75c0172410c66952", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm6.gno", -// "IsMethod": false, -// "Name": "init.1", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm6.gno", -// "Line": "11", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" // }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm6.gno", -// "IsMethod": false, -// "Name": "main", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm6.gno", -// "Line": "16", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "131993c49dced230bd7071e9bae8d95e28733b73", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" // } -// ] +// } // } diff --git a/gnovm/tests/files/zrealm7.gno b/gnovm/tests/files/zrealm7.gno index e22b90f0e0f..5b932962db4 100644 --- a/gnovm/tests/files/zrealm7.gno +++ b/gnovm/tests/files/zrealm7.gno @@ -164,26 +164,7 @@ func main() { // "RefCount": "1" // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:8]={ -// "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", -// "ModTime": "9", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", -// "RefCount": "1" -// }, -// "Value": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "2c172bbe0183ccc73c59d9acb196c45b0331c39e", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9" -// } -// } -// } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:7]={ +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={ // "Fields": [ // { // "T": { @@ -192,7 +173,7 @@ func main() { // }, // "V": { // "@type": "/gno.StringValue", -// "value": "key1" +// "value": "key0" // } // }, // { @@ -202,11 +183,11 @@ func main() { // }, // "V": { // "@type": "/gno.StringValue", -// "value": "value1" +// "value": "value0" // } // }, // { -// "N": "AwAAAAAAAAA=", +// "N": "AQAAAAAAAAA=", // "T": { // "@type": "/gno.PrimitiveType", // "value": "32" @@ -219,16 +200,6 @@ func main() { // "@type": "/gno.RefType", // "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" // } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "a4fa9bdf45caf8c6b5be7a3752704423817b3ef2", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" -// }, -// "Index": "0", -// "TV": null // } // }, // { @@ -238,27 +209,55 @@ func main() { // "@type": "/gno.RefType", // "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" // } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "43f69f24b7827a331921b4af0f667346d186e0c3", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8" -// }, -// "Index": "0", -// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", // "ModTime": "9", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", // "RefCount": "1" // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={ +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", +// "ModTime": "9", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "d7fbb234dca9f194f35fe5409a62db9daf39b0fc", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" +// } +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:8]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "ModTime": "9", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "2c172bbe0183ccc73c59d9acb196c45b0331c39e", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9" +// } +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:7]={ // "Fields": [ // { // "T": { @@ -267,7 +266,7 @@ func main() { // }, // "V": { // "@type": "/gno.StringValue", -// "value": "key0" +// "value": "key1" // } // }, // { @@ -277,11 +276,11 @@ func main() { // }, // "V": { // "@type": "/gno.StringValue", -// "value": "value0" +// "value": "value1" // } // }, // { -// "N": "AQAAAAAAAAA=", +// "N": "AwAAAAAAAAA=", // "T": { // "@type": "/gno.PrimitiveType", // "value": "32" @@ -294,6 +293,16 @@ func main() { // "@type": "/gno.RefType", // "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" // } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "76a40dcf03d32c312c2213265c14d4de1b12a810", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" +// }, +// "Index": "0", +// "TV": null // } // }, // { @@ -303,13 +312,23 @@ func main() { // "@type": "/gno.RefType", // "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" // } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "43f69f24b7827a331921b4af0f667346d186e0c3", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8" +// }, +// "Index": "0", +// "TV": null // } // } // ], // "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", // "ModTime": "9", -// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", // "RefCount": "1" // } // } @@ -327,121 +346,8 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "f56fbd9c8db299689cc0cf806fe741b6a6e641e6", +// "Hash": "92b2f4ebab764951f64086bce480f898f755de5a", // "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" // } // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ -// "Blank": {}, -// "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", -// "IsEscaped": true, -// "ModTime": "9", -// "RefCount": "2" -// }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "github.com/gnolang/gno/_test/timtadh/data_structures/tree/avl.AvlNode" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "450aef9858564ed4ec1c418f1e8dac828079016b", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm7.gno", -// "IsMethod": false, -// "Name": "init.1", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm7.gno", -// "Line": "11", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm7.gno", -// "IsMethod": false, -// "Name": "main", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm7.gno", -// "Line": "17", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// } -// ] -// } diff --git a/gnovm/tests/files/zrealm_avl0.gno b/gnovm/tests/files/zrealm_avl0.gno index 362d04ec0f8..1db1adebd3e 100644 --- a/gnovm/tests/files/zrealm_avl0.gno +++ b/gnovm/tests/files/zrealm_avl0.gno @@ -215,116 +215,3 @@ func main() { // } // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ -// "Blank": {}, -// "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", -// "IsEscaped": true, -// "ModTime": "5", -// "RefCount": "2" -// }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "ae86874f9b47fa5e64c30b3e92e9d07f2ec967a4", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm_avl0.gno", -// "IsMethod": false, -// "Name": "init.1", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm_avl0.gno", -// "Line": "10", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm_avl0.gno", -// "IsMethod": false, -// "Name": "main", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm_avl0.gno", -// "Line": "15", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// } -// ] -// } diff --git a/gnovm/tests/files/zrealm_avl1.gno b/gnovm/tests/files/zrealm_avl1.gno index bfcf9f7975e..572c49333bc 100644 --- a/gnovm/tests/files/zrealm_avl1.gno +++ b/gnovm/tests/files/zrealm_avl1.gno @@ -24,6 +24,44 @@ func main() { // Realm: // switchrealm["gno.land/r/test"] +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:6]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "ModTime": "11", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "1375f6f96a1a3f298347dc8fc0065afa36cb7f0f", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" +// } +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:8]={ +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "ModTime": "13", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "b28057ab7be6383785c0a5503e8a531bdbc21851", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9" +// } +// } +// } // c[a8ada09dee16d791fd406d629fe29bb0ed084a30:15]={ // "Fields": [ // { @@ -143,7 +181,7 @@ func main() { // "@type": "/gno.PointerValue", // "Base": { // "@type": "/gno.RefValue", -// "Hash": "2f3adc5d0f2a3fe0331cfa93572a7abdde14c9aa", +// "Hash": "cafae89e4d4aaaefe7fdf0691084508d4274a981", // "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8" // }, // "Index": "0", @@ -191,7 +229,7 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "fe20a19f956511f274dc77854e9e5468387260f4", +// "Hash": "b2e446f490656c19a83c43055de29c96e92a1549", // "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:13" // } // } @@ -235,7 +273,7 @@ func main() { // "@type": "/gno.PointerValue", // "Base": { // "@type": "/gno.RefValue", -// "Hash": "c89a71bdf045e8bde2059dc9d33839f916e02e5d", +// "Hash": "4e56eeb96eb1d9b27cf603140cd03a1622b6358b", // "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" // }, // "Index": "0", @@ -254,7 +292,7 @@ func main() { // "@type": "/gno.PointerValue", // "Base": { // "@type": "/gno.RefValue", -// "Hash": "90fa67f8c47db4b9b2a60425dff08d5a3385100f", +// "Hash": "7b61530859954d1d14b2f696c91c5f37d39c21e7", // "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:12" // }, // "Index": "0", @@ -283,123 +321,10 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "83e42caaf53070dd95b5f859053eb51ed900bbda", +// "Hash": "fedc6d430b38c985dc6a985b2fcaee97e88ba6da", // "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:11" // } // } // } -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ -// "Blank": {}, -// "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", -// "IsEscaped": true, -// "ModTime": "9", -// "RefCount": "2" -// }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "1faa9fa4ba1935121a6d3f0a623772e9d4499b0a", -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm_avl1.gno", -// "IsMethod": false, -// "Name": "init.1", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm_avl1.gno", -// "Line": "10", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm_avl1.gno", -// "IsMethod": false, -// "Name": "main", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm_avl1.gno", -// "Line": "15", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// } -// ] -// } // d[a8ada09dee16d791fd406d629fe29bb0ed084a30:4] // d[a8ada09dee16d791fd406d629fe29bb0ed084a30:5] diff --git a/gnovm/tests/files/zrealm_crossrealm21.gno b/gnovm/tests/files/zrealm_crossrealm21.gno index 634fbea13c8..e3b29f671a4 100644 --- a/gnovm/tests/files/zrealm_crossrealm21.gno +++ b/gnovm/tests/files/zrealm_crossrealm21.gno @@ -24,673 +24,25 @@ func main() { // Realm: // switchrealm["gno.land/r/demo/tests/crossrealm"] -// u[1712ac7adcfdc8e58a67e5615e20fb312394c4df:2]={ -// "Blank": {}, +// u[0edc46caf30c00efd87b6c272673239eafbd051e:3]={ // "ObjectInfo": { -// "ID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:2", +// "ID": "0edc46caf30c00efd87b6c272673239eafbd051e:3", // "IsEscaped": true, // "ModTime": "5", +// "OwnerID": "0edc46caf30c00efd87b6c272673239eafbd051e:2", // "RefCount": "2" // }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.StructType", -// "Fields": [ -// { -// "Embedded": false, -// "Name": "A", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// } -// ], -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// }, -// "Methods": [ -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "ls", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.LocalStruct" -// } -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": null, -// "FileName": "crossrealm.gno", -// "IsMethod": true, -// "Name": "String", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "12", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "ls", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.LocalStruct" -// } -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ] -// } -// } -// } -// ], -// "Name": "LocalStruct", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.LocalStruct" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "a75fdb389fedfcbbaa7f446d528c1e149726347c", -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:4" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "init.2", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "19", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/tests/p_crossrealm.Container" -// } -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "Make1", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "24", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/tests/p_crossrealm.Container" -// } -// } -// } -// ] -// } -// } +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/tests/crossrealm_b.fooer" // }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.InterfaceType", -// "Generic": "", -// "Methods": [ -// { -// "Embedded": false, -// "Name": "Foo", -// "Tag": "", -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// ], -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// }, -// "Methods": [], -// "Name": "Fooer", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm_b.fooer" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0edc46caf30c00efd87b6c272673239eafbd051e:3" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "f", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "SetFooer", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "35", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "f", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "GetFooer", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "40", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "CallFooerFoo", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "42", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// }, -// "Methods": [], -// "Name": "FooerGetter", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "fg", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "SetFooerGetter", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "48", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "fg", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "GetFooerGetter", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "53", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "CallFooerGetterFoo", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "57", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "8222b763e9bc04b4b7805e165e9f1324a39f28b6", +// "ObjectID": "0edc46caf30c00efd87b6c272673239eafbd051e:4" // } -// ] +// } // } // switchrealm["gno.land/r/demo/tests/crossrealm_b"] // switchrealm["gno.land/r/demo/tests/crossrealm"] diff --git a/gnovm/tests/files/zrealm_crossrealm22.gno b/gnovm/tests/files/zrealm_crossrealm22.gno index 18985f7719d..afb9dab6d59 100644 --- a/gnovm/tests/files/zrealm_crossrealm22.gno +++ b/gnovm/tests/files/zrealm_crossrealm22.gno @@ -38,744 +38,25 @@ func main() { // Realm: // switchrealm["gno.land/r/demo/tests/crossrealm"] -// c[1712ac7adcfdc8e58a67e5615e20fb312394c4df:6]={ -// "Blank": {}, +// u[0edc46caf30c00efd87b6c272673239eafbd051e:3]={ // "ObjectInfo": { -// "ID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:6", -// "ModTime": "0", -// "OwnerID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:2", -// "RefCount": "1" -// }, -// "Parent": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "f5a516808f8976c33939133293d598ce3bca4e8d:3" -// }, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm_crossrealm22.gno", -// "Line": "11", -// "PkgPath": "gno.land/r/crossrealm_test" -// } -// }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm_b.fooer" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0edc46caf30c00efd87b6c272673239eafbd051e:3" -// }, -// "Index": "0", -// "TV": null -// } -// } -// ] -// } -// u[1712ac7adcfdc8e58a67e5615e20fb312394c4df:2]={ -// "Blank": {}, -// "ObjectInfo": { -// "ID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:2", +// "ID": "0edc46caf30c00efd87b6c272673239eafbd051e:3", // "IsEscaped": true, -// "ModTime": "5", +// "ModTime": "6", +// "OwnerID": "0edc46caf30c00efd87b6c272673239eafbd051e:2", // "RefCount": "2" // }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/tests/crossrealm_b.fooer" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "8222b763e9bc04b4b7805e165e9f1324a39f28b6", +// "ObjectID": "0edc46caf30c00efd87b6c272673239eafbd051e:4" // } -// }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.StructType", -// "Fields": [ -// { -// "Embedded": false, -// "Name": "A", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// } -// ], -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// }, -// "Methods": [ -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "ls", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.LocalStruct" -// } -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": null, -// "FileName": "crossrealm.gno", -// "IsMethod": true, -// "Name": "String", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "12", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "ls", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.LocalStruct" -// } -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ] -// } -// } -// } -// ], -// "Name": "LocalStruct", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.LocalStruct" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "a75fdb389fedfcbbaa7f446d528c1e149726347c", -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:4" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "init.2", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "19", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/tests/p_crossrealm.Container" -// } -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "Make1", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "24", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/tests/p_crossrealm.Container" -// } -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.InterfaceType", -// "Generic": "", -// "Methods": [ -// { -// "Embedded": false, -// "Name": "Foo", -// "Tag": "", -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// ], -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// }, -// "Methods": [], -// "Name": "Fooer", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "f", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "SetFooer", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "35", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "f", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "GetFooer", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "40", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "CallFooerFoo", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "42", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// }, -// "Methods": [], -// "Name": "FooerGetter", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Hash": "23de97a577d573252d00394ce9b71c24b0646546", -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:6" -// }, -// "FileName": "", -// "IsMethod": false, -// "Name": "", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/crossrealm_test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "28", -// "File": "files/zrealm_crossrealm22.gno", -// "Line": "13", -// "PkgPath": "gno.land/r/crossrealm_test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "fg", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "SetFooerGetter", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "48", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "fg", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "GetFooerGetter", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "53", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "CallFooerGetterFoo", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "57", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// } -// ] +// } // } // switchrealm["gno.land/r/crossrealm_test"] // switchrealm["gno.land/r/demo/tests/crossrealm_b"] @@ -826,701 +107,26 @@ func main() { // } // } // switchrealm["gno.land/r/demo/tests/crossrealm"] -// u[1712ac7adcfdc8e58a67e5615e20fb312394c4df:2]={ -// "Blank": {}, +// u[0edc46caf30c00efd87b6c272673239eafbd051e:3]={ // "ObjectInfo": { -// "ID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:2", +// "ID": "0edc46caf30c00efd87b6c272673239eafbd051e:3", // "IsEscaped": true, // "ModTime": "6", -// "RefCount": "2" -// }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } +// "OwnerID": "0edc46caf30c00efd87b6c272673239eafbd051e:2", +// "RefCount": "1" // }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.StructType", -// "Fields": [ -// { -// "Embedded": false, -// "Name": "A", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// } -// ], -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// }, -// "Methods": [ -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "ls", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.LocalStruct" -// } -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": null, -// "FileName": "crossrealm.gno", -// "IsMethod": true, -// "Name": "String", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "12", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "ls", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.LocalStruct" -// } -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ] -// } -// } -// } -// ], -// "Name": "LocalStruct", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.LocalStruct" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "a75fdb389fedfcbbaa7f446d528c1e149726347c", -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:4" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "init.2", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "19", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/tests/p_crossrealm.Container" -// } -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "Make1", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "24", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/tests/p_crossrealm.Container" -// } -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.InterfaceType", -// "Generic": "", -// "Methods": [ -// { -// "Embedded": false, -// "Name": "Foo", -// "Tag": "", -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// ], -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// }, -// "Methods": [], -// "Name": "Fooer", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "f", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "SetFooer", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "35", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "f", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "GetFooer", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "40", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "CallFooerFoo", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "42", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// }, -// "Methods": [], -// "Name": "FooerGetter", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0edc46caf30c00efd87b6c272673239eafbd051e:5" -// }, -// "FileName": "", -// "IsMethod": false, -// "Name": "", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm_b", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "23", -// "File": "crossrealm.gno", -// "Line": "23", -// "PkgPath": "gno.land/r/demo/tests/crossrealm_b" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "fg", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "SetFooerGetter", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "48", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "fg", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "GetFooerGetter", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "53", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "CallFooerGetterFoo", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "57", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/tests/crossrealm_b.fooer" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "759fd5b507fff8ea1b18d401550d918387a63445", +// "ObjectID": "0edc46caf30c00efd87b6c272673239eafbd051e:4" // } -// ] +// } // } -// d[1712ac7adcfdc8e58a67e5615e20fb312394c4df:6] // switchrealm["gno.land/r/demo/tests/crossrealm_b"] // switchrealm["gno.land/r/demo/tests/crossrealm_b"] // switchrealm["gno.land/r/demo/tests/crossrealm"] @@ -1547,732 +153,6 @@ func main() { // } // switchrealm["gno.land/r/demo/tests/crossrealm_b"] // switchrealm["gno.land/r/demo/tests/crossrealm"] -// c[1712ac7adcfdc8e58a67e5615e20fb312394c4df:7]={ -// "Blank": {}, -// "ObjectInfo": { -// "ID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:7", -// "ModTime": "0", -// "OwnerID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:2", -// "RefCount": "1" -// }, -// "Parent": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0edc46caf30c00efd87b6c272673239eafbd051e:5" -// }, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "23", -// "File": "crossrealm.gno", -// "Line": "24", -// "PkgPath": "gno.land/r/demo/tests/crossrealm_b" -// } -// }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ] -// } -// u[1712ac7adcfdc8e58a67e5615e20fb312394c4df:2]={ -// "Blank": {}, -// "ObjectInfo": { -// "ID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:2", -// "IsEscaped": true, -// "ModTime": "6", -// "RefCount": "2" -// }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.StructType", -// "Fields": [ -// { -// "Embedded": false, -// "Name": "A", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// } -// ], -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// }, -// "Methods": [ -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "ls", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.LocalStruct" -// } -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": null, -// "FileName": "crossrealm.gno", -// "IsMethod": true, -// "Name": "String", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "12", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "ls", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.LocalStruct" -// } -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ] -// } -// } -// } -// ], -// "Name": "LocalStruct", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.LocalStruct" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "a75fdb389fedfcbbaa7f446d528c1e149726347c", -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:4" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "init.2", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "19", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/tests/p_crossrealm.Container" -// } -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "Make1", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "24", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/tests/p_crossrealm.Container" -// } -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.InterfaceType", -// "Generic": "", -// "Methods": [ -// { -// "Embedded": false, -// "Name": "Foo", -// "Tag": "", -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// ], -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// }, -// "Methods": [], -// "Name": "Fooer", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "f", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "SetFooer", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "35", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "f", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "GetFooer", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "40", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "CallFooerFoo", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "42", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// }, -// "Methods": [], -// "Name": "FooerGetter", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Hash": "89352b352826005a86eee78e6c832b43ae0ab6a6", -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:7" -// }, -// "FileName": "", -// "IsMethod": false, -// "Name": "", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm_b", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "62", -// "File": "crossrealm.gno", -// "Line": "24", -// "PkgPath": "gno.land/r/demo/tests/crossrealm_b" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.Fooer" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "fg", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "SetFooerGetter", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "48", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "fg", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "GetFooerGetter", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "53", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests/crossrealm.FooerGetter" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "1712ac7adcfdc8e58a67e5615e20fb312394c4df:3" -// }, -// "FileName": "crossrealm.gno", -// "IsMethod": false, -// "Name": "CallFooerGetterFoo", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests/crossrealm", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "crossrealm.gno", -// "Line": "57", -// "PkgPath": "gno.land/r/demo/tests/crossrealm" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// } -// ] -// } // switchrealm["gno.land/r/demo/tests/crossrealm_b"] // switchrealm["gno.land/r/demo/tests/crossrealm_b"] // switchrealm["gno.land/r/demo/tests/crossrealm"] diff --git a/gnovm/tests/files/zrealm_natbind0.gno b/gnovm/tests/files/zrealm_natbind0.gno index e6ebef6252e..6f8045107dc 100644 --- a/gnovm/tests/files/zrealm_natbind0.gno +++ b/gnovm/tests/files/zrealm_natbind0.gno @@ -28,153 +28,3 @@ func main() { // Realm: // switchrealm["gno.land/r/test"] -// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ -// "Blank": {}, -// "ObjectInfo": { -// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", -// "IsEscaped": true, -// "ModTime": "3", -// "RefCount": "2" -// }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a7f5397443359ea76c50be82c77f1f893a060925:10" -// }, -// "FileName": "native.gno", -// "IsMethod": false, -// "Name": "GetChainID", -// "NativeName": "GetChainID", -// "NativePkg": "std", -// "PkgPath": "std", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "native.gno", -// "Line": "13", -// "PkgPath": "std" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm_natbind0.gno", -// "IsMethod": false, -// "Name": "init.1", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm_natbind0.gno", -// "Line": "10", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" -// }, -// "FileName": "files/zrealm_natbind0.gno", -// "IsMethod": false, -// "Name": "main", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/test", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "files/zrealm_natbind0.gno", -// "Line": "14", -// "PkgPath": "gno.land/r/test" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// } -// ] -// } diff --git a/gnovm/tests/files/zrealm_tests0.gno b/gnovm/tests/files/zrealm_tests0.gno index afb7e4a7c3b..872046ef795 100644 --- a/gnovm/tests/files/zrealm_tests0.gno +++ b/gnovm/tests/files/zrealm_tests0.gno @@ -80,7 +80,7 @@ func main() { // "@type": "/gno.PointerValue", // "Base": { // "@type": "/gno.RefValue", -// "Hash": "148d314678615253ee2032d7ecff6b144b474baf", +// "Hash": "4e0d77a91ba35733bf82329317bf8c8dffa6f655", // "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:12" // }, // "Index": "0", @@ -133,1644 +133,43 @@ func main() { // "RefCount": "1" // } // } -// u[0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:2]={ -// "Blank": {}, +// u[0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:12]={ // "ObjectInfo": { -// "ID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:2", -// "IsEscaped": true, -// "ModTime": "16", -// "RefCount": "5" +// "ID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:12", +// "ModTime": "17", +// "OwnerID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:11", +// "RefCount": "1" // }, -// "Parent": null, -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "0", -// "File": "", -// "Line": "0", -// "PkgPath": "gno.land/r/demo/tests" +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/tests_foo.FooStringer" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "f163acee63433b44deb3168fef87beb588847322", +// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:13" // } +// } +// } +// u[0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:15]={ +// "ObjectInfo": { +// "ID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:15", +// "ModTime": "17", +// "OwnerID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:14", +// "RefCount": "1" // }, -// "Values": [ -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.InterfaceType", -// "Generic": "", -// "Methods": [ -// { -// "Embedded": false, -// "Name": "String", -// "Tag": "", -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ] -// } -// } -// ], -// "PkgPath": "gno.land/r/demo/tests" -// }, -// "Methods": [], -// "Name": "Stringer", -// "PkgPath": "gno.land/r/demo/tests" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.SliceType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.Stringer" -// }, -// "Vrd": false -// }, -// "V": { -// "@type": "/gno.SliceValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "3c58838c5667649add1ff8ee48a1cdc187fcd2ef", -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:17" -// }, -// "Length": "3", -// "Maxcap": "3", -// "Offset": "0" -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "str", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.Stringer" -// } -// } -// ], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:3" -// }, -// "FileName": "interfaces.gno", -// "IsMethod": false, -// "Name": "AddStringer", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "interfaces.gno", -// "Line": "13", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "str", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.Stringer" -// } -// } -// ], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "path", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:3" -// }, -// "FileName": "interfaces.gno", -// "IsMethod": false, -// "Name": "Render", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "interfaces.gno", -// "Line": "20", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "path", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.PrimitiveType", -// "value": "2048" -// }, -// "Methods": [], -// "Name": "Word", -// "PkgPath": "gno.land/r/demo/tests" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.SliceType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.Word" -// }, -// "Vrd": false -// }, -// "Methods": [ -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "n", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.nat" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.nat" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": null, -// "FileName": "realm_method38d.gno", -// "IsMethod": true, -// "Name": "Add", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "realm_method38d.gno", -// "Line": "5", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "n", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.nat" -// } -// } -// ], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.nat" -// } -// } -// ] -// } -// } -// } -// ], -// "Name": "nat", -// "PkgPath": "gno.land/r/demo/tests" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.Int" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "49283398258e135138cd8e234142d5daaa8c661d", -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:4" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.StructType", -// "Fields": [ -// { -// "Embedded": false, -// "Name": "neg", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "4" -// } -// }, -// { -// "Embedded": false, -// "Name": "abs", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.nat" -// } -// } -// ], -// "PkgPath": "gno.land/r/demo/tests" -// }, -// "Methods": [], -// "Name": "Int", -// "PkgPath": "gno.land/r/demo/tests" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.nat" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:7" -// }, -// "FileName": "realm_compositelit.gno", -// "IsMethod": false, -// "Name": "GetZeroType", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "realm_compositelit.gno", -// "Line": "19", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.nat" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.nat" -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.nat" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:8" -// }, -// "FileName": "realm_method38d.gno", -// "IsMethod": false, -// "Name": "GetAbs", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "realm_method38d.gno", -// "Line": "9", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.nat" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.nat" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:8" -// }, -// "FileName": "realm_method38d.gno", -// "IsMethod": false, -// "Name": "AbsAdd", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "realm_method38d.gno", -// "Line": "15", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.nat" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" -// }, -// "FileName": "tests.gno", -// "IsMethod": false, -// "Name": "IncCounter", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "tests.gno", -// "Line": "12", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" -// }, -// "FileName": "tests.gno", -// "IsMethod": false, -// "Name": "Counter", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "tests.gno", -// "Line": "16", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" -// }, -// "FileName": "tests.gno", -// "IsMethod": false, -// "Name": "CurrentRealmPath", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "tests.gno", -// "Line": "20", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "std.Address" -// }, -// "V": { -// "@type": "/gno.StringValue", -// "value": "g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "std.Address" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" -// }, -// "FileName": "tests.gno", -// "IsMethod": false, -// "Name": "InitOrigCaller", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "tests.gno", -// "Line": "26", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "std.Address" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" -// }, -// "FileName": "tests.gno", -// "IsMethod": false, -// "Name": "CallAssertOriginCall", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "tests.gno", -// "Line": "30", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "4" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" -// }, -// "FileName": "tests.gno", -// "IsMethod": false, -// "Name": "CallIsOriginCall", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "tests.gno", -// "Line": "34", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "4" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" -// }, -// "FileName": "tests.gno", -// "IsMethod": false, -// "Name": "CallSubtestsAssertOriginCall", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "tests.gno", -// "Line": "38", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "4" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" -// }, -// "FileName": "tests.gno", -// "IsMethod": false, -// "Name": "CallSubtestsIsOriginCall", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "tests.gno", -// "Line": "42", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "4" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.StructType", -// "Fields": [ -// { -// "Embedded": false, -// "Name": "Field", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ], -// "PkgPath": "gno.land/r/demo/tests" -// }, -// "Methods": [ -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "t", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.TestRealmObject" -// } -// } -// } -// ], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": null, -// "FileName": "tests.gno", -// "IsMethod": true, -// "Name": "Modify", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "tests.gno", -// "Line": "59", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "t", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.TestRealmObject" -// } -// } -// } -// ], -// "Results": [] -// } -// } -// } -// ], -// "Name": "TestRealmObject", -// "PkgPath": "gno.land/r/demo/tests" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.TestRealmObject" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "5e56ba76fc0add1a3a67f7a8b6709f4f27215f93", -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:10" -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "t", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.TestRealmObject" -// } -// } -// } -// ], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" -// }, -// "FileName": "tests.gno", -// "IsMethod": false, -// "Name": "ModifyTestRealmObject", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "tests.gno", -// "Line": "55", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "t", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.TestRealmObject" -// } -// } -// } -// ], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.TypeType" -// }, -// "V": { -// "@type": "/gno.TypeValue", -// "Type": { -// "@type": "/gno.DeclaredType", -// "Base": { -// "@type": "/gno.StructType", -// "Fields": [ -// { -// "Embedded": false, -// "Name": "Name", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// }, -// { -// "Embedded": false, -// "Name": "Child", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.TestNode" -// } -// } -// } -// ], -// "PkgPath": "gno.land/r/demo/tests" -// }, -// "Methods": [], -// "Name": "TestNode", -// "PkgPath": "gno.land/r/demo/tests" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.TestNode" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.TestNode" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.TestNode" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" -// }, -// "FileName": "tests.gno", -// "IsMethod": false, -// "Name": "InitTestNodes", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "tests.gno", -// "Line": "77", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" -// }, -// "FileName": "tests.gno", -// "IsMethod": false, -// "Name": "ModTestNodes", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "tests.gno", -// "Line": "82", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" -// }, -// "FileName": "tests.gno", -// "IsMethod": false, -// "Name": "PrintTestNodes", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "tests.gno", -// "Line": "90", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "std.Realm" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" -// }, -// "FileName": "tests.gno", -// "IsMethod": false, -// "Name": "GetPrevRealm", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "tests.gno", -// "Line": "94", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "std.Realm" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "std.Realm" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" -// }, -// "FileName": "tests.gno", -// "IsMethod": false, -// "Name": "GetRSubtestsPrevRealm", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "tests.gno", -// "Line": "98", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.RefType", -// "ID": "std.Realm" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "fn", -// "Tag": "", -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// ], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" -// }, -// "FileName": "tests.gno", -// "IsMethod": false, -// "Name": "Exec", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "tests.gno", -// "Line": "102", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "fn", -// "Tag": "", -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [] -// } -// } -// ], -// "Results": [] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "4" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" -// }, -// "FileName": "tests.gno", -// "IsMethod": false, -// "Name": "IsCallerSubPath", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "tests.gno", -// "Line": "106", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "4" -// } -// } -// ] -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "4" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" -// }, -// "FileName": "tests.gno", -// "IsMethod": false, -// "Name": "IsCallerParentPath", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "tests.gno", -// "Line": "110", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "4" -// } -// } -// ] -// } -// } +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/tests_foo.FooStringer" // }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "4" -// } -// } -// ] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:9" -// }, -// "FileName": "tests.gno", -// "IsMethod": false, -// "Name": "HasCallerSameNamespace", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "Column": "1", -// "File": "tests.gno", -// "Line": "114", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [], -// "Results": [ -// { -// "Embedded": false, -// "Name": "", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "4" -// } -// } -// ] -// } -// } +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "2a96c074210eb2db31b7941e31d62deb04e59937", +// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:16" // } -// ] +// } // } // d[0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:14] // switchrealm["gno.land/r/demo/tests_foo"] From 8cca690ae01f22f12683a6e4bf2f1265ac4cff04 Mon Sep 17 00:00:00 2001 From: Nemanja Matic <106317308+Nemanya8@users.noreply.github.com> Date: Wed, 15 Jan 2025 05:05:02 -0500 Subject: [PATCH 14/20] feat(cmd/gno): allow gno test to default to the current directory (#3453) This PR resolves https://github.com/gnolang/gno/issues/3420 by enhancing the gno test command. The command now assumes the current directory (.) as the default path when no directory is specified, aligning its behavior with go test. Changes to CI: - Removed the no_args test. - Added new tests to cover the following scenarios: - Valid test execution. - Valid file-based test. - Test with flags. - Empty directory. - Empty test file. This PR is a repost of https://github.com/gnolang/gno/pull/3429 with a cleaner commit history. CC @notJoon @moul --------- Co-authored-by: Nemanya21 Co-authored-by: Leon Hudak <33522493+leohhhn@users.noreply.github.com> --- gnovm/cmd/gno/test.go | 6 +- gnovm/cmd/gno/testdata/test/no_args.txtar | 6 -- .../gno/testdata/test/no_path_empty_dir.txtar | 6 ++ .../gno/testdata/test/no_path_empty_gno.txtar | 8 ++ .../gno/testdata/test/no_path_flag_run.txtar | 99 +++++++++++++++++++ 5 files changed, 117 insertions(+), 8 deletions(-) delete mode 100644 gnovm/cmd/gno/testdata/test/no_args.txtar create mode 100644 gnovm/cmd/gno/testdata/test/no_path_empty_dir.txtar create mode 100644 gnovm/cmd/gno/testdata/test/no_path_empty_gno.txtar create mode 100644 gnovm/cmd/gno/testdata/test/no_path_flag_run.txtar diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index fec0de7c221..ea06b25d8e2 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -146,8 +146,9 @@ func (c *testCfg) RegisterFlags(fs *flag.FlagSet) { } func execTest(cfg *testCfg, args []string, io commands.IO) error { - if len(args) < 1 { - return flag.ErrHelp + // Default to current directory if no args provided + if len(args) == 0 { + args = []string{"."} } // guess opts.RootDir @@ -159,6 +160,7 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { if err != nil { return fmt.Errorf("list targets from patterns: %w", err) } + if len(paths) == 0 { io.ErrPrintln("no packages to test") return nil diff --git a/gnovm/cmd/gno/testdata/test/no_args.txtar b/gnovm/cmd/gno/testdata/test/no_args.txtar deleted file mode 100644 index bd9cd4fc965..00000000000 --- a/gnovm/cmd/gno/testdata/test/no_args.txtar +++ /dev/null @@ -1,6 +0,0 @@ -# Run gno test without args - -! gno test - -! stdout .+ -stderr 'USAGE' diff --git a/gnovm/cmd/gno/testdata/test/no_path_empty_dir.txtar b/gnovm/cmd/gno/testdata/test/no_path_empty_dir.txtar new file mode 100644 index 00000000000..6f8b54d7ea4 --- /dev/null +++ b/gnovm/cmd/gno/testdata/test/no_path_empty_dir.txtar @@ -0,0 +1,6 @@ +# Run gno test without path argument on an empty dir + +gno test + +! stdout .+ +stderr '[no test files]' \ No newline at end of file diff --git a/gnovm/cmd/gno/testdata/test/no_path_empty_gno.txtar b/gnovm/cmd/gno/testdata/test/no_path_empty_gno.txtar new file mode 100644 index 00000000000..846ce5bbd88 --- /dev/null +++ b/gnovm/cmd/gno/testdata/test/no_path_empty_gno.txtar @@ -0,0 +1,8 @@ +# Test empty gno without path argument + +gno test + +! stdout .+ +stderr '\? \. \[no test files\]' + +-- empty.gno -- \ No newline at end of file diff --git a/gnovm/cmd/gno/testdata/test/no_path_flag_run.txtar b/gnovm/cmd/gno/testdata/test/no_path_flag_run.txtar new file mode 100644 index 00000000000..3db2a4c9295 --- /dev/null +++ b/gnovm/cmd/gno/testdata/test/no_path_flag_run.txtar @@ -0,0 +1,99 @@ +# Run test on gno.land/p/demo/ufmt without path argument + +gno test + +gno test -v + +! stdout .+ +stderr '=== RUN TestRun/hello' +stderr '=== RUN TestRun/hi_you' +stderr '=== RUN TestRun/hi_me' +stderr '=== RUN TestRun' +stderr '--- PASS: TestRun' + +gno test -v -run .* + +! stdout .+ +stderr '=== RUN TestRun/hello' +stderr '=== RUN TestRun/hi_you' +stderr '=== RUN TestRun/hi_me' +stderr '=== RUN TestRun' +stderr '--- PASS: TestRun' + +gno test -v -run NotExists + +! stdout .+ +! stderr '=== RUN TestRun' + +gno test -v -run .*/hello + +! stdout .+ +stderr '=== RUN TestRun/hello' +! stderr '=== RUN TestRun/hi_you' +! stderr '=== RUN TestRun/hi_me' +stderr '=== RUN TestRun' +stderr '--- PASS: TestRun' + +gno test -v -run .*/hi + +! stdout .+ +! stderr '=== RUN TestRun/hello' +stderr '=== RUN TestRun/hi_you' +stderr '=== RUN TestRun/hi_me' +stderr '=== RUN TestRun' +stderr '--- PASS: TestRun' + +gno test -v -run .*/NotExists + +! stdout .+ +stderr '=== RUN TestRun' +stderr '--- PASS: TestRun' + +gno test -v -run Run/.* + +! stdout .+ +stderr '=== RUN TestRun/hello' +stderr '=== RUN TestRun/hi_you' +stderr '=== RUN TestRun/hi_me' +stderr '=== RUN TestRun' +stderr '--- PASS: TestRun' + +gno test -v -run Run/ + +! stdout .+ +stderr '=== RUN TestRun/hello' +stderr '=== RUN TestRun/hi_you' +stderr '=== RUN TestRun/hi_me' +stderr '=== RUN TestRun' +stderr '--- PASS: TestRun' + +gno test -v -run Run/hello + +! stdout .+ +stderr '=== RUN TestRun/hello' +! stderr '=== RUN TestRun/hi_you' +! stderr '=== RUN TestRun/hi_me' +stderr '=== RUN TestRun' +stderr '--- PASS: TestRun' + +-- run.gno -- +package run + +-- run_test.gno -- +package run + +import ( + "fmt" + "testing" +) + +func TestRun(t *testing.T) { + cases := []string { + "hello", + "hi you", + "hi me", + } + for _, tc := range cases { + t.Run(tc, func(t *testing.T) {}) + } +} \ No newline at end of file From ae0c9f40340b854c0a033b8552dfa78ec9650b9b Mon Sep 17 00:00:00 2001 From: Alexis Colin Date: Wed, 15 Jan 2025 19:13:21 +0900 Subject: [PATCH 15/20] fix(gnoweb): fix mobile breadcrumb (#3516) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes the breadcrumb bar of gnoweb on mobile. Before: Capture d’écran 2025-01-15 à 16 02 30 After: Capture d’écran 2025-01-15 à 16 03 05 --- gno.land/pkg/gnoweb/components/breadcrumb.gohtml | 6 +++--- gno.land/pkg/gnoweb/public/styles.css | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gno.land/pkg/gnoweb/components/breadcrumb.gohtml b/gno.land/pkg/gnoweb/components/breadcrumb.gohtml index 9295b17b6f5..3824eb5894f 100644 --- a/gno.land/pkg/gnoweb/components/breadcrumb.gohtml +++ b/gno.land/pkg/gnoweb/components/breadcrumb.gohtml @@ -2,15 +2,15 @@
    {{- range $index, $part := .Parts }} {{- if $index }} -
  1. +
  2. {{- else }} -
  3. +
  4. {{- end }} {{ $part.Name }}
  5. {{- end }} {{- if .Args }} -
  6. +
  7. {{ .Args }}
  8. {{- end }} diff --git a/gno.land/pkg/gnoweb/public/styles.css b/gno.land/pkg/gnoweb/public/styles.css index a6771695454..4ff1a266c0c 100644 --- a/gno.land/pkg/gnoweb/public/styles.css +++ b/gno.land/pkg/gnoweb/public/styles.css @@ -1,3 +1,3 @@ @font-face{font-family:Roboto;font-style:normal;font-weight:900;font-display:swap;src:url(fonts/roboto/roboto-mono-normal.woff2) format("woff2"),url(fonts/roboto/roboto-mono-normal.woff) format("woff")}@font-face{font-family:Inter var;font-weight:100 900;font-display:block;font-style:oblique 0deg 10deg;src:url(fonts/intervar/Intervar.woff2) format("woff2")}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: } -/*! tailwindcss v3.4.14 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #bdbdbd}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#7c7c7c}input::placeholder,textarea::placeholder{opacity:1;color:#7c7c7c}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}html{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));font-family:Inter var,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji,sans-serif;font-size:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;-moz-osx-font-smoothing:grayscale;font-smoothing:antialiased;font-variant-ligatures:contextual common-ligatures;font-kerning:normal;text-rendering:optimizeLegibility}svg{max-height:100%;max-width:100%}form{margin-top:0;margin-bottom:0}.realm-content{overflow-wrap:break-word;padding-top:2.5rem;font-size:1rem}.realm-content>:first-child{margin-top:0!important}.realm-content a{font-weight:500;--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.realm-content a:hover{text-decoration-line:underline}.realm-content h1,.realm-content h2,.realm-content h3,.realm-content h4{margin-top:3rem;line-height:1.25;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-content h2,.realm-content h2 *{font-weight:700}.realm-content h3,.realm-content h3 *,.realm-content h4,.realm-content h4 *{font-weight:600}.realm-content h1+h2,.realm-content h2+h3,.realm-content h3+h4{margin-top:1rem}.realm-content h1{font-size:2.375rem;font-weight:700}.realm-content h2{font-size:1.5rem}.realm-content h3{margin-top:2.5rem;font-size:1.25rem}.realm-content h3,.realm-content h4{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content h4{margin-top:1.5rem;margin-bottom:1.5rem;font-size:1.125rem;font-weight:500}.realm-content p{margin-top:1.25rem;margin-bottom:1.25rem}.realm-content strong{font-weight:700;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-content strong *{font-weight:700}.realm-content em{font-style:oblique 10deg}.realm-content blockquote{margin-top:1rem;margin-bottom:1rem;border-left-width:4px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-style:oblique 10deg}.realm-content ol,.realm-content ul{margin-top:1.5rem;margin-bottom:1.5rem;padding-left:1rem}.realm-content ol li,.realm-content ul li{margin-bottom:.5rem}.realm-content img{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-content figure{margin-top:1.5rem;margin-bottom:1.5rem;text-align:center}.realm-content figcaption{font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content :not(pre)>code{border-radius:.25rem;background-color:rgb(226 226 226/var(--tw-bg-opacity));padding:.125rem .25rem;font-size:.96em}.realm-content :not(pre)>code,.realm-content pre{--tw-bg-opacity:1;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-content pre{overflow-x:auto;border-radius:.375rem;background-color:rgb(240 240 240/var(--tw-bg-opacity));padding:1rem}.realm-content hr{margin-top:2.5rem;margin-bottom:2.5rem;border-top-width:1px;--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.realm-content table{margin-top:2rem;margin-bottom:2rem;width:100%;border-collapse:collapse}.realm-content td,.realm-content th{border-width:1px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding:.5rem 1rem}.realm-content th{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));font-weight:700}.realm-content caption{margin-top:.5rem;text-align:left;font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content q{margin-top:1.5rem;margin-bottom:1.5rem;border-left-width:4px;--tw-border-opacity:1;border-left-color:rgb(204 204 204/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(85 85 85/var(--tw-text-opacity));font-style:oblique 10deg;quotes:"“" "”" "‘" "’"}.realm-content q:after,.realm-content q:before{margin-right:.25rem;font-size:1.5rem;--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity));content:open-quote;vertical-align:-.4rem}.realm-content q:after{content:close-quote}.realm-content q:before{content:open-quote}.realm-content q:after{content:close-quote}.realm-content ol ol,.realm-content ol ul,.realm-content ul ol,.realm-content ul ul{margin-top:.75rem;margin-bottom:.5rem;padding-left:1rem}.realm-content ul{list-style-type:disc}.realm-content ol{list-style-type:decimal}.realm-content table th:first-child,.realm-content td:first-child{padding-left:0}.realm-content table th:last-child,.realm-content td:last-child{padding-right:0}.realm-content abbr[title]{cursor:help;border-bottom-width:1px;border-style:dotted}.realm-content details{margin-top:1.25rem;margin-bottom:1.25rem}.realm-content summary{cursor:pointer;font-weight:700}.realm-content a code{color:inherit}.realm-content video{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-content math{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-content small{font-size:.875rem}.realm-content del{text-decoration-line:line-through}.realm-content sub{vertical-align:sub;font-size:.75rem}.realm-content sup{vertical-align:super;font-size:.75rem}.realm-content button,.realm-content input{border-width:1px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding:.5rem 1rem}main :is(h1,h2,h3,h4){scroll-margin-top:6rem}::-moz-selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}::selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.sidemenu .peer:checked+label>svg{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.toc-expend-btn:has(#toc-expend:checked)+nav{display:block}.toc-expend-btn:has(#toc-expend:checked) .toc-expend-btn_ico{--tw-rotate:180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.main-header:has(#sidemenu-docs:checked)+main #sidebar #sidebar-docs,.main-header:has(#sidemenu-meta:checked)+main #sidebar #sidebar-meta,.main-header:has(#sidemenu-source:checked)+main #sidebar #sidebar-source,.main-header:has(#sidemenu-summary:checked)+main #sidebar #sidebar-summary{display:block}@media (min-width:40rem){:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .main-navigation,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main .realm-content{grid-column:span 6/span 6}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .sidemenu,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar{grid-column:span 4/span 4}}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar:before{position:absolute;top:0;left:-1.75rem;z-index:-1;display:block;height:100%;width:50vw;--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));--tw-content:"";content:var(--tw-content)}main :is(.source-code)>pre{overflow:scroll;border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(255 255 255/var(--tw-bg-opacity))!important;padding:1rem .25rem;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-size:.875rem}@media (min-width:40rem){main :is(.source-code)>pre{padding:2rem .75rem;font-size:1rem}}main .realm-content>pre a:hover{text-decoration-line:none}main :is(.realm-content,.source-code)>pre .chroma-ln:target{background-color:transparent!important}main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-ln:target),main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-ln:target) .chroma-cl,main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover),main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover) .chroma-cl{border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(226 226 226/var(--tw-bg-opacity))!important}main :is(.realm-content,.source-code)>pre .chroma-ln{scroll-margin-top:6rem}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.bottom-1{bottom:.25rem}.left-0{left:0}.right-2{right:.5rem}.right-3{right:.75rem}.top-0{top:0}.top-1\/2{top:50%}.top-14{top:3.5rem}.top-2{top:.5rem}.z-1{z-index:1}.z-max{z-index:9999}.col-span-1{grid-column:span 1/span 1}.col-span-10{grid-column:span 10/span 10}.col-span-3{grid-column:span 3/span 3}.col-span-7{grid-column:span 7/span 7}.row-span-1{grid-row:span 1/span 1}.row-start-1{grid-row-start:1}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-8{margin-bottom:2rem}.mr-10{margin-right:2.5rem}.mt-1{margin-top:.25rem}.mt-10{margin-top:2.5rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-full{height:100%}.max-h-screen{max-height:100vh}.min-h-full{min-height:100%}.min-h-screen{min-height:100vh}.w-10{width:2.5rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-full{width:100%}.min-w-2{min-width:.5rem}.min-w-48{min-width:12rem}.max-w-screen-max{max-width:98.75rem}.shrink-0{flex-shrink:0}.grow-\[2\]{flex-grow:2}.-translate-y-1\/2{--tw-translate-y:-50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.list-none{list-style-type:none}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-flow-dense{grid-auto-flow:dense}.auto-rows-min{grid-auto-rows:min-content}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-8{gap:2rem}.gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-y-2{row-gap:.5rem}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.whitespace-pre-wrap{white-space:pre-wrap}.rounded{border-radius:.375rem}.rounded-sm{border-radius:.25rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-t{border-top-width:1px}.border-gray-100{--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.bg-gray-300{--tw-bg-opacity:1;background-color:rgb(153 153 153/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.bg-light{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-transparent{background-color:transparent}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-4{padding:1rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-10{padding-left:2.5rem;padding-right:2.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-px{padding-top:1px;padding-bottom:1px}.pb-24{padding-bottom:6rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pb-6{padding-bottom:1.5rem}.pb-8{padding-bottom:2rem}.pl-4{padding-left:1rem}.pr-10{padding-right:2.5rem}.pt-0\.5{padding-top:.125rem}.pt-2{padding-top:.5rem}.font-mono{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.text-100{font-size:.875rem}.text-200{font-size:1rem}.text-50{font-size:.75rem}.text-600{font-size:1.5rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.capitalize{text-transform:capitalize}.leading-tight{line-height:1.25}.text-gray-300{--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(124 124 124/var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity:1;color:rgb(19 19 19/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.text-light{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.outline-none{outline:2px solid transparent;outline-offset:2px}.text-stroke{-webkit-text-stroke:currentColor;-webkit-text-stroke-width:.6px}.no-scrollbar::-webkit-scrollbar{display:none}.no-scrollbar{-ms-overflow-style:none;scrollbar-width:none}.\*\:pl-0>*{padding-left:0}.before\:px-\[0\.18rem\]:before{content:var(--tw-content);padding-left:.18rem;padding-right:.18rem}.before\:text-gray-300:before{content:var(--tw-content);--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.before\:content-\[\'\/\'\]:before{--tw-content:"/";content:var(--tw-content)}.before\:content-\[\'\:\'\]:before{--tw-content:":";content:var(--tw-content)}.before\:content-\[\'open\'\]:before{--tw-content:"open";content:var(--tw-content)}.after\:pointer-events-none:after{content:var(--tw-content);pointer-events:none}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:bottom-0:after{content:var(--tw-content);bottom:0}.after\:left-0:after{content:var(--tw-content);left:0}.after\:top-0:after{content:var(--tw-content);top:0}.after\:block:after{content:var(--tw-content);display:block}.after\:h-1:after{content:var(--tw-content);height:.25rem}.after\:h-full:after{content:var(--tw-content);height:100%}.after\:w-full:after{content:var(--tw-content);width:100%}.after\:rounded-t-sm:after{content:var(--tw-content);border-top-left-radius:.25rem;border-top-right-radius:.25rem}.after\:bg-gray-100:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.after\:bg-green-600:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.first\:border-t:first-child{border-top-width:1px}.hover\:border-gray-300:hover{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.hover\:bg-green-600:hover{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.hover\:text-gray-600:hover{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.hover\:text-green-600:hover{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.hover\:text-light:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.hover\:underline:hover{text-decoration-line:underline}.focus\:border-gray-300:focus{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.focus\:border-l-gray-300:focus{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-gray-300{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-l-gray-300{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group.is-active .group-\[\.is-active\]\:text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.peer:checked~.peer-checked\:before\:content-\[\'close\'\]:before{--tw-content:"close";content:var(--tw-content)}.peer:focus-within~.peer-focus-within\:hidden{display:none}.has-\[ul\:empty\]\:hidden:has(ul:empty){display:none}.has-\[\:focus-within\]\:border-gray-300:has(:focus-within){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.has-\[\:focus\]\:border-gray-300:has(:focus){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}@media (min-width:30rem){.sm\:gap-6{gap:1.5rem}}@media (min-width:40rem){.md\:col-span-3{grid-column:span 3/span 3}.md\:mb-0{margin-bottom:0}.md\:h-4{height:1rem}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:items-center{align-items:center}.md\:gap-x-8{-moz-column-gap:2rem;column-gap:2rem}.md\:px-10{padding-left:2.5rem;padding-right:2.5rem}.md\:pb-0{padding-bottom:0}}@media (min-width:51.25rem){.lg\:order-2{order:2}.lg\:col-span-3{grid-column:span 3/span 3}.lg\:col-span-7{grid-column:span 7/span 7}.lg\:row-span-2{grid-row:span 2/span 2}.lg\:row-start-1{grid-row-start:1}.lg\:row-start-2{grid-row-start:2}.lg\:mb-4{margin-bottom:1rem}.lg\:mt-0{margin-top:0}.lg\:mt-10{margin-top:2.5rem}.lg\:block{display:block}.lg\:hidden{display:none}.lg\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.lg\:flex-row{flex-direction:row}.lg\:justify-start{justify-content:flex-start}.lg\:justify-between{justify-content:space-between}.lg\:gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.lg\:border-none{border-style:none}.lg\:bg-transparent{background-color:transparent}.lg\:p-0{padding:0}.lg\:px-0{padding-left:0;padding-right:0}.lg\:px-2{padding-left:.5rem;padding-right:.5rem}.lg\:py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.lg\:pb-28{padding-bottom:7rem}.lg\:pt-2{padding-top:.5rem}.lg\:text-200{font-size:1rem}.lg\:font-semibold{font-weight:600}.lg\:hover\:bg-transparent:hover{background-color:transparent}}@media (min-width:63.75rem){.xl\:inline{display:inline}.xl\:hidden{display:none}.xl\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xl\:flex-row{flex-direction:row}.xl\:items-center{align-items:center}.xl\:gap-20{gap:5rem}.xl\:gap-6{gap:1.5rem}.xl\:pt-0{padding-top:0}}@media (min-width:85.375rem){.xxl\:inline-block{display:inline-block}.xxl\:h-4{height:1rem}.xxl\:w-4{width:1rem}.xxl\:gap-20{gap:5rem}.xxl\:gap-x-32{-moz-column-gap:8rem;column-gap:8rem}.xxl\:pr-1{padding-right:.25rem}} \ No newline at end of file +/*! tailwindcss v3.4.14 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #bdbdbd}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#7c7c7c}input::placeholder,textarea::placeholder{opacity:1;color:#7c7c7c}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}html{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));font-family:Inter var,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji,sans-serif;font-size:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;-moz-osx-font-smoothing:grayscale;font-smoothing:antialiased;font-variant-ligatures:contextual common-ligatures;font-kerning:normal;text-rendering:optimizeLegibility}svg{max-height:100%;max-width:100%}form{margin-top:0;margin-bottom:0}.realm-content{overflow-wrap:break-word;padding-top:2.5rem;font-size:1rem}.realm-content>:first-child{margin-top:0!important}.realm-content a{font-weight:500;--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.realm-content a:hover{text-decoration-line:underline}.realm-content h1,.realm-content h2,.realm-content h3,.realm-content h4{margin-top:3rem;line-height:1.25;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-content h2,.realm-content h2 *{font-weight:700}.realm-content h3,.realm-content h3 *,.realm-content h4,.realm-content h4 *{font-weight:600}.realm-content h1+h2,.realm-content h2+h3,.realm-content h3+h4{margin-top:1rem}.realm-content h1{font-size:2.375rem;font-weight:700}.realm-content h2{font-size:1.5rem}.realm-content h3{margin-top:2.5rem;font-size:1.25rem}.realm-content h3,.realm-content h4{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content h4{margin-top:1.5rem;margin-bottom:1.5rem;font-size:1.125rem;font-weight:500}.realm-content p{margin-top:1.25rem;margin-bottom:1.25rem}.realm-content strong{font-weight:700;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-content strong *{font-weight:700}.realm-content em{font-style:oblique 10deg}.realm-content blockquote{margin-top:1rem;margin-bottom:1rem;border-left-width:4px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-style:oblique 10deg}.realm-content ol,.realm-content ul{margin-top:1.5rem;margin-bottom:1.5rem;padding-left:1rem}.realm-content ol li,.realm-content ul li{margin-bottom:.5rem}.realm-content img{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-content figure{margin-top:1.5rem;margin-bottom:1.5rem;text-align:center}.realm-content figcaption{font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content :not(pre)>code{border-radius:.25rem;background-color:rgb(226 226 226/var(--tw-bg-opacity));padding:.125rem .25rem;font-size:.96em}.realm-content :not(pre)>code,.realm-content pre{--tw-bg-opacity:1;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-content pre{overflow-x:auto;border-radius:.375rem;background-color:rgb(240 240 240/var(--tw-bg-opacity));padding:1rem}.realm-content hr{margin-top:2.5rem;margin-bottom:2.5rem;border-top-width:1px;--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.realm-content table{margin-top:2rem;margin-bottom:2rem;width:100%;border-collapse:collapse}.realm-content td,.realm-content th{border-width:1px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding:.5rem 1rem}.realm-content th{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));font-weight:700}.realm-content caption{margin-top:.5rem;text-align:left;font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content q{margin-top:1.5rem;margin-bottom:1.5rem;border-left-width:4px;--tw-border-opacity:1;border-left-color:rgb(204 204 204/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(85 85 85/var(--tw-text-opacity));font-style:oblique 10deg;quotes:"“" "”" "‘" "’"}.realm-content q:after,.realm-content q:before{margin-right:.25rem;font-size:1.5rem;--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity));content:open-quote;vertical-align:-.4rem}.realm-content q:after{content:close-quote}.realm-content q:before{content:open-quote}.realm-content q:after{content:close-quote}.realm-content ol ol,.realm-content ol ul,.realm-content ul ol,.realm-content ul ul{margin-top:.75rem;margin-bottom:.5rem;padding-left:1rem}.realm-content ul{list-style-type:disc}.realm-content ol{list-style-type:decimal}.realm-content table th:first-child,.realm-content td:first-child{padding-left:0}.realm-content table th:last-child,.realm-content td:last-child{padding-right:0}.realm-content abbr[title]{cursor:help;border-bottom-width:1px;border-style:dotted}.realm-content details{margin-top:1.25rem;margin-bottom:1.25rem}.realm-content summary{cursor:pointer;font-weight:700}.realm-content a code{color:inherit}.realm-content video{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-content math{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-content small{font-size:.875rem}.realm-content del{text-decoration-line:line-through}.realm-content sub{vertical-align:sub;font-size:.75rem}.realm-content sup{vertical-align:super;font-size:.75rem}.realm-content button,.realm-content input{border-width:1px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding:.5rem 1rem}main :is(h1,h2,h3,h4){scroll-margin-top:6rem}::-moz-selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}::selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.sidemenu .peer:checked+label>svg{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.toc-expend-btn:has(#toc-expend:checked)+nav{display:block}.toc-expend-btn:has(#toc-expend:checked) .toc-expend-btn_ico{--tw-rotate:180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.main-header:has(#sidemenu-docs:checked)+main #sidebar #sidebar-docs,.main-header:has(#sidemenu-meta:checked)+main #sidebar #sidebar-meta,.main-header:has(#sidemenu-source:checked)+main #sidebar #sidebar-source,.main-header:has(#sidemenu-summary:checked)+main #sidebar #sidebar-summary{display:block}@media (min-width:40rem){:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .main-navigation,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main .realm-content{grid-column:span 6/span 6}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .sidemenu,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar{grid-column:span 4/span 4}}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar:before{position:absolute;top:0;left:-1.75rem;z-index:-1;display:block;height:100%;width:50vw;--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));--tw-content:"";content:var(--tw-content)}main :is(.source-code)>pre{overflow:scroll;border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(255 255 255/var(--tw-bg-opacity))!important;padding:1rem .25rem;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-size:.875rem}@media (min-width:40rem){main :is(.source-code)>pre{padding:2rem .75rem;font-size:1rem}}main .realm-content>pre a:hover{text-decoration-line:none}main :is(.realm-content,.source-code)>pre .chroma-ln:target{background-color:transparent!important}main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-ln:target),main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-ln:target) .chroma-cl,main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover),main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover) .chroma-cl{border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(226 226 226/var(--tw-bg-opacity))!important}main :is(.realm-content,.source-code)>pre .chroma-ln{scroll-margin-top:6rem}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.bottom-1{bottom:.25rem}.left-0{left:0}.right-2{right:.5rem}.right-3{right:.75rem}.top-0{top:0}.top-1\/2{top:50%}.top-14{top:3.5rem}.top-2{top:.5rem}.z-1{z-index:1}.z-max{z-index:9999}.col-span-1{grid-column:span 1/span 1}.col-span-10{grid-column:span 10/span 10}.col-span-3{grid-column:span 3/span 3}.col-span-7{grid-column:span 7/span 7}.row-span-1{grid-row:span 1/span 1}.row-start-1{grid-row-start:1}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-8{margin-bottom:2rem}.mr-10{margin-right:2.5rem}.mt-1{margin-top:.25rem}.mt-10{margin-top:2.5rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-full{height:100%}.max-h-screen{max-height:100vh}.min-h-full{min-height:100%}.min-h-screen{min-height:100vh}.w-10{width:2.5rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-full{width:100%}.min-w-2{min-width:.5rem}.min-w-48{min-width:12rem}.max-w-screen-max{max-width:98.75rem}.shrink-0{flex-shrink:0}.grow-\[2\]{flex-grow:2}.-translate-y-1\/2{--tw-translate-y:-50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.list-none{list-style-type:none}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-flow-dense{grid-auto-flow:dense}.auto-rows-min{grid-auto-rows:min-content}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-8{gap:2rem}.gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-y-2{row-gap:.5rem}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.rounded{border-radius:.375rem}.rounded-sm{border-radius:.25rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-t{border-top-width:1px}.border-gray-100{--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.bg-gray-300{--tw-bg-opacity:1;background-color:rgb(153 153 153/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.bg-light{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-transparent{background-color:transparent}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-4{padding:1rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-10{padding-left:2.5rem;padding-right:2.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-px{padding-top:1px;padding-bottom:1px}.pb-24{padding-bottom:6rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pb-6{padding-bottom:1.5rem}.pb-8{padding-bottom:2rem}.pl-4{padding-left:1rem}.pr-10{padding-right:2.5rem}.pt-0\.5{padding-top:.125rem}.pt-2{padding-top:.5rem}.font-mono{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.text-100{font-size:.875rem}.text-200{font-size:1rem}.text-50{font-size:.75rem}.text-600{font-size:1.5rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.capitalize{text-transform:capitalize}.leading-tight{line-height:1.25}.text-gray-300{--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(124 124 124/var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity:1;color:rgb(19 19 19/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.text-light{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.outline-none{outline:2px solid transparent;outline-offset:2px}.text-stroke{-webkit-text-stroke:currentColor;-webkit-text-stroke-width:.6px}.no-scrollbar::-webkit-scrollbar{display:none}.no-scrollbar{-ms-overflow-style:none;scrollbar-width:none}.\*\:pl-0>*{padding-left:0}.before\:px-\[0\.18rem\]:before{content:var(--tw-content);padding-left:.18rem;padding-right:.18rem}.before\:text-gray-300:before{content:var(--tw-content);--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.before\:content-\[\'\/\'\]:before{--tw-content:"/";content:var(--tw-content)}.before\:content-\[\'\:\'\]:before{--tw-content:":";content:var(--tw-content)}.before\:content-\[\'open\'\]:before{--tw-content:"open";content:var(--tw-content)}.after\:pointer-events-none:after{content:var(--tw-content);pointer-events:none}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:bottom-0:after{content:var(--tw-content);bottom:0}.after\:left-0:after{content:var(--tw-content);left:0}.after\:top-0:after{content:var(--tw-content);top:0}.after\:block:after{content:var(--tw-content);display:block}.after\:h-1:after{content:var(--tw-content);height:.25rem}.after\:h-full:after{content:var(--tw-content);height:100%}.after\:w-full:after{content:var(--tw-content);width:100%}.after\:rounded-t-sm:after{content:var(--tw-content);border-top-left-radius:.25rem;border-top-right-radius:.25rem}.after\:bg-gray-100:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.after\:bg-green-600:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.first\:border-t:first-child{border-top-width:1px}.hover\:border-gray-300:hover{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.hover\:bg-green-600:hover{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.hover\:text-gray-600:hover{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.hover\:text-green-600:hover{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.hover\:text-light:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.hover\:underline:hover{text-decoration-line:underline}.focus\:border-gray-300:focus{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.focus\:border-l-gray-300:focus{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-gray-300{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-l-gray-300{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group.is-active .group-\[\.is-active\]\:text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.peer:checked~.peer-checked\:before\:content-\[\'close\'\]:before{--tw-content:"close";content:var(--tw-content)}.peer:focus-within~.peer-focus-within\:hidden{display:none}.has-\[ul\:empty\]\:hidden:has(ul:empty){display:none}.has-\[\:focus-within\]\:border-gray-300:has(:focus-within){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.has-\[\:focus\]\:border-gray-300:has(:focus){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}@media (min-width:30rem){.sm\:gap-6{gap:1.5rem}}@media (min-width:40rem){.md\:col-span-3{grid-column:span 3/span 3}.md\:mb-0{margin-bottom:0}.md\:h-4{height:1rem}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:items-center{align-items:center}.md\:gap-x-8{-moz-column-gap:2rem;column-gap:2rem}.md\:px-10{padding-left:2.5rem;padding-right:2.5rem}.md\:pb-0{padding-bottom:0}}@media (min-width:51.25rem){.lg\:order-2{order:2}.lg\:col-span-3{grid-column:span 3/span 3}.lg\:col-span-7{grid-column:span 7/span 7}.lg\:row-span-2{grid-row:span 2/span 2}.lg\:row-start-1{grid-row-start:1}.lg\:row-start-2{grid-row-start:2}.lg\:mb-4{margin-bottom:1rem}.lg\:mt-0{margin-top:0}.lg\:mt-10{margin-top:2.5rem}.lg\:block{display:block}.lg\:hidden{display:none}.lg\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.lg\:flex-row{flex-direction:row}.lg\:justify-start{justify-content:flex-start}.lg\:justify-between{justify-content:space-between}.lg\:gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.lg\:border-none{border-style:none}.lg\:bg-transparent{background-color:transparent}.lg\:p-0{padding:0}.lg\:px-0{padding-left:0;padding-right:0}.lg\:px-2{padding-left:.5rem;padding-right:.5rem}.lg\:py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.lg\:pb-28{padding-bottom:7rem}.lg\:pt-2{padding-top:.5rem}.lg\:text-200{font-size:1rem}.lg\:font-semibold{font-weight:600}.lg\:hover\:bg-transparent:hover{background-color:transparent}}@media (min-width:63.75rem){.xl\:inline{display:inline}.xl\:hidden{display:none}.xl\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xl\:flex-row{flex-direction:row}.xl\:items-center{align-items:center}.xl\:gap-20{gap:5rem}.xl\:gap-6{gap:1.5rem}.xl\:pt-0{padding-top:0}}@media (min-width:85.375rem){.xxl\:inline-block{display:inline-block}.xxl\:h-4{height:1rem}.xxl\:w-4{width:1rem}.xxl\:gap-20{gap:5rem}.xxl\:gap-x-32{-moz-column-gap:8rem;column-gap:8rem}.xxl\:pr-1{padding-right:.25rem}} \ No newline at end of file From f91eb4ace354b0798752d06eb228311d4b490147 Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Wed, 15 Jan 2025 15:41:27 +0100 Subject: [PATCH 16/20] feat(examples): add rolist (#3400) - [x] add `avl/rolist` - [x] add `I...` interfaces for `avl/...`'s main structs. --------- Signed-off-by: moul <94029+moul@users.noreply.github.com> --- examples/gno.land/p/demo/avl/list/list.gno | 16 ++ examples/gno.land/p/demo/avl/rolist/gno.mod | 1 + .../gno.land/p/demo/avl/rolist/rolist.gno | 119 +++++++++++++ .../p/demo/avl/rolist/rolist_test.gno | 162 ++++++++++++++++++ .../gno.land/p/demo/avl/rotree/rotree.gno | 19 +- 5 files changed, 315 insertions(+), 2 deletions(-) create mode 100644 examples/gno.land/p/demo/avl/rolist/gno.mod create mode 100644 examples/gno.land/p/demo/avl/rolist/rolist.gno create mode 100644 examples/gno.land/p/demo/avl/rolist/rolist_test.gno diff --git a/examples/gno.land/p/demo/avl/list/list.gno b/examples/gno.land/p/demo/avl/list/list.gno index 0875eb66e01..594f5fa2a1f 100644 --- a/examples/gno.land/p/demo/avl/list/list.gno +++ b/examples/gno.land/p/demo/avl/list/list.gno @@ -41,6 +41,22 @@ import ( "gno.land/p/demo/seqid" ) +// IList defines the interface for list operations +type IList interface { + Len() int + Append(values ...interface{}) + Get(index int) interface{} + Set(index int, value interface{}) bool + Delete(index int) (interface{}, bool) + Slice(startIndex, endIndex int) []interface{} + ForEach(fn func(index int, value interface{}) bool) + Clone() *List + DeleteRange(startIndex, endIndex int) int +} + +// Verify List implements IList interface +var _ IList = (*List)(nil) + // List represents an ordered sequence of items backed by an AVL tree type List struct { tree avl.Tree diff --git a/examples/gno.land/p/demo/avl/rolist/gno.mod b/examples/gno.land/p/demo/avl/rolist/gno.mod new file mode 100644 index 00000000000..682513c2cc3 --- /dev/null +++ b/examples/gno.land/p/demo/avl/rolist/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/avl/rolist diff --git a/examples/gno.land/p/demo/avl/rolist/rolist.gno b/examples/gno.land/p/demo/avl/rolist/rolist.gno new file mode 100644 index 00000000000..23a85d9c885 --- /dev/null +++ b/examples/gno.land/p/demo/avl/rolist/rolist.gno @@ -0,0 +1,119 @@ +// Package rolist provides a read-only wrapper for list.List with safe value transformation. +// +// It is useful when you want to expose a read-only view of a list while ensuring that +// the sensitive data cannot be modified. +// +// Example: +// +// // Define a user structure with sensitive data +// type User struct { +// Name string +// Balance int +// Internal string // sensitive field +// } +// +// // Create and populate the original list +// privateList := list.New() +// privateList.Append(&User{ +// Name: "Alice", +// Balance: 100, +// Internal: "sensitive", +// }) +// +// // Create a safe transformation function that copies the struct +// // while excluding sensitive data +// makeEntrySafeFn := func(v interface{}) interface{} { +// u := v.(*User) +// return &User{ +// Name: u.Name, +// Balance: u.Balance, +// Internal: "", // omit sensitive data +// } +// } +// +// // Create a read-only view of the list +// publicList := rolist.Wrap(list, makeEntrySafeFn) +// +// // Safely access the data +// value := publicList.Get(0) +// user := value.(*User) +// // user.Name == "Alice" +// // user.Balance == 100 +// // user.Internal == "" (sensitive data is filtered) +package rolist + +import ( + "gno.land/p/demo/avl/list" +) + +// IReadOnlyList defines the read-only operations available on a list. +type IReadOnlyList interface { + Len() int + Get(index int) interface{} + Slice(startIndex, endIndex int) []interface{} + ForEach(fn func(index int, value interface{}) bool) +} + +// ReadOnlyList wraps a list.List and provides read-only access. +type ReadOnlyList struct { + list *list.List + makeEntrySafeFn func(interface{}) interface{} +} + +// Verify interface implementations +var _ IReadOnlyList = (*ReadOnlyList)(nil) +var _ IReadOnlyList = (interface{ list.IList })(nil) // is subset of list.IList + +// Wrap creates a new ReadOnlyList from an existing list.List and a safety transformation function. +// If makeEntrySafeFn is nil, values will be returned as-is without transformation. +func Wrap(list *list.List, makeEntrySafeFn func(interface{}) interface{}) *ReadOnlyList { + return &ReadOnlyList{ + list: list, + makeEntrySafeFn: makeEntrySafeFn, + } +} + +// getSafeValue applies the makeEntrySafeFn if it exists, otherwise returns the original value +func (rol *ReadOnlyList) getSafeValue(value interface{}) interface{} { + if rol.makeEntrySafeFn == nil { + return value + } + return rol.makeEntrySafeFn(value) +} + +// Len returns the number of elements in the list. +func (rol *ReadOnlyList) Len() int { + return rol.list.Len() +} + +// Get returns the value at the specified index, converted to a safe format. +// Returns nil if index is out of bounds. +func (rol *ReadOnlyList) Get(index int) interface{} { + value := rol.list.Get(index) + if value == nil { + return nil + } + return rol.getSafeValue(value) +} + +// Slice returns a slice of values from startIndex (inclusive) to endIndex (exclusive), +// with all values converted to a safe format. +func (rol *ReadOnlyList) Slice(startIndex, endIndex int) []interface{} { + values := rol.list.Slice(startIndex, endIndex) + if values == nil { + return nil + } + + result := make([]interface{}, len(values)) + for i, v := range values { + result[i] = rol.getSafeValue(v) + } + return result +} + +// ForEach iterates through all elements in the list, providing safe versions of the values. +func (rol *ReadOnlyList) ForEach(fn func(index int, value interface{}) bool) { + rol.list.ForEach(func(index int, value interface{}) bool { + return fn(index, rol.getSafeValue(value)) + }) +} diff --git a/examples/gno.land/p/demo/avl/rolist/rolist_test.gno b/examples/gno.land/p/demo/avl/rolist/rolist_test.gno new file mode 100644 index 00000000000..03b0a8cba30 --- /dev/null +++ b/examples/gno.land/p/demo/avl/rolist/rolist_test.gno @@ -0,0 +1,162 @@ +package rolist + +import ( + "testing" + + "gno.land/p/demo/avl/list" +) + +func TestExample(t *testing.T) { + // User represents our internal data structure + type User struct { + ID string + Name string + Balance int + Internal string // sensitive internal data + } + + // Create and populate the original list + l := &list.List{} + l.Append( + &User{ + ID: "1", + Name: "Alice", + Balance: 100, + Internal: "sensitive_data_1", + }, + &User{ + ID: "2", + Name: "Bob", + Balance: 200, + Internal: "sensitive_data_2", + }, + ) + + // Define a makeEntrySafeFn that: + // 1. Creates a defensive copy of the User struct + // 2. Omits sensitive internal data + makeEntrySafeFn := func(v interface{}) interface{} { + originalUser := v.(*User) + return &User{ + ID: originalUser.ID, + Name: originalUser.Name, + Balance: originalUser.Balance, + Internal: "", // Omit sensitive data + } + } + + // Create a read-only view of the list + roList := Wrap(l, makeEntrySafeFn) + + // Test retrieving and verifying a user + t.Run("Get User", func(t *testing.T) { + // Get user from read-only list + value := roList.Get(0) + if value == nil { + t.Fatal("User at index 0 not found") + } + + user := value.(*User) + + // Verify user data is correct + if user.Name != "Alice" || user.Balance != 100 { + t.Errorf("Unexpected user data: got name=%s balance=%d", user.Name, user.Balance) + } + + // Verify sensitive data is not exposed + if user.Internal != "" { + t.Error("Sensitive data should not be exposed") + } + + // Verify it's a different instance than the original + originalUser := l.Get(0).(*User) + if user == originalUser { + t.Error("Read-only list should return a copy, not the original pointer") + } + }) + + // Test slice functionality + t.Run("Slice Users", func(t *testing.T) { + users := roList.Slice(0, 2) + if len(users) != 2 { + t.Fatalf("Expected 2 users, got %d", len(users)) + } + + for _, v := range users { + user := v.(*User) + if user.Internal != "" { + t.Error("Sensitive data exposed in slice") + } + } + }) + + // Test ForEach functionality + t.Run("ForEach Users", func(t *testing.T) { + count := 0 + roList.ForEach(func(index int, value interface{}) bool { + user := value.(*User) + if user.Internal != "" { + t.Error("Sensitive data exposed during iteration") + } + count++ + return false + }) + + if count != 2 { + t.Errorf("Expected 2 users, got %d", count) + } + }) +} + +func TestNilMakeEntrySafeFn(t *testing.T) { + // Create a list with some test data + l := &list.List{} + originalValue := []int{1, 2, 3} + l.Append(originalValue) + + // Create a ReadOnlyList with nil makeEntrySafeFn + roList := Wrap(l, nil) + + // Test that we get back the original value + value := roList.Get(0) + if value == nil { + t.Fatal("Value not found") + } + + // Verify it's the exact same slice (not a copy) + retrievedSlice := value.([]int) + if &retrievedSlice[0] != &originalValue[0] { + t.Error("Expected to get back the original slice reference") + } +} + +func TestReadOnlyList(t *testing.T) { + // Example of a makeEntrySafeFn that appends "_readonly" to demonstrate transformation + makeEntrySafeFn := func(value interface{}) interface{} { + return value.(string) + "_readonly" + } + + l := &list.List{} + l.Append("value1", "value2", "value3") + + roList := Wrap(l, makeEntrySafeFn) + + tests := []struct { + name string + index int + expected interface{} + }{ + {"ExistingIndex0", 0, "value1_readonly"}, + {"ExistingIndex1", 1, "value2_readonly"}, + {"NonExistingIndex", 3, nil}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + value := roList.Get(tt.index) + if value != tt.expected { + t.Errorf("For index %d, expected %v, got %v", tt.index, tt.expected, value) + } + }) + } +} diff --git a/examples/gno.land/p/demo/avl/rotree/rotree.gno b/examples/gno.land/p/demo/avl/rotree/rotree.gno index 3e093c4d0e0..17cb4e20ced 100644 --- a/examples/gno.land/p/demo/avl/rotree/rotree.gno +++ b/examples/gno.land/p/demo/avl/rotree/rotree.gno @@ -82,8 +82,23 @@ type ReadOnlyTree struct { makeEntrySafeFn func(interface{}) interface{} } -// Verify that ReadOnlyTree implements ITree -var _ avl.ITree = (*ReadOnlyTree)(nil) +// IReadOnlyTree defines the read-only operations available on a tree. +type IReadOnlyTree interface { + Size() int + Has(key string) bool + Get(key string) (interface{}, bool) + GetByIndex(index int) (string, interface{}) + Iterate(start, end string, cb avl.IterCbFn) bool + ReverseIterate(start, end string, cb avl.IterCbFn) bool + IterateByOffset(offset int, count int, cb avl.IterCbFn) bool + ReverseIterateByOffset(offset int, count int, cb avl.IterCbFn) bool +} + +// Verify that ReadOnlyTree implements both ITree and IReadOnlyTree +var ( + _ avl.ITree = (*ReadOnlyTree)(nil) + _ IReadOnlyTree = (*ReadOnlyTree)(nil) +) // getSafeValue applies the makeEntrySafeFn if it exists, otherwise returns the original value func (roTree *ReadOnlyTree) getSafeValue(value interface{}) interface{} { From 1ff162bd8df47246e01ecfe973702271fde6436f Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Wed, 15 Jan 2025 15:42:59 +0100 Subject: [PATCH 17/20] feat(examples): add p/moul/collection (#3321) Addresses #1467 Related with #3317 --------- Signed-off-by: moul <94029+moul@users.noreply.github.com> --- .../gno.land/p/demo/avl/list/list_test.gno | 64 ++ .../gno.land/p/moul/collection/collection.gno | 509 +++++++++ .../p/moul/collection/collection_test.gno | 987 ++++++++++++++++++ examples/gno.land/p/moul/collection/entry.gno | 149 +++ examples/gno.land/p/moul/collection/gno.mod | 1 + 5 files changed, 1710 insertions(+) create mode 100644 examples/gno.land/p/moul/collection/collection.gno create mode 100644 examples/gno.land/p/moul/collection/collection_test.gno create mode 100644 examples/gno.land/p/moul/collection/entry.gno create mode 100644 examples/gno.land/p/moul/collection/gno.mod diff --git a/examples/gno.land/p/demo/avl/list/list_test.gno b/examples/gno.land/p/demo/avl/list/list_test.gno index 265fbdb5eb1..0293692f660 100644 --- a/examples/gno.land/p/demo/avl/list/list_test.gno +++ b/examples/gno.land/p/demo/avl/list/list_test.gno @@ -2,6 +2,8 @@ package list import ( "testing" + + "gno.land/p/demo/ufmt" ) func TestList_Basic(t *testing.T) { @@ -395,6 +397,68 @@ func TestList_IndexConsistency(t *testing.T) { } } +func TestList_RecursiveSafety(t *testing.T) { + // Create a new list + l := &List{} + + // Add some initial values + l.Append("id1") + l.Append("id2") + l.Append("id3") + + // Test deep list traversal + found := false + l.ForEach(func(i int, v interface{}) bool { + if str, ok := v.(string); ok { + if str == "id2" { + found = true + return true // stop iteration + } + } + return false // continue iteration + }) + + if !found { + t.Error("Failed to find expected value in list") + } + + short := testing.Short() + + // Test recursive safety by performing multiple operations + for i := 0; i < 1000; i++ { + // Add new value + l.Append(ufmt.Sprintf("id%d", i+4)) + + if !short { + // Search for a value + var lastFound bool + l.ForEach(func(j int, v interface{}) bool { + if str, ok := v.(string); ok { + if str == ufmt.Sprintf("id%d", i+3) { + lastFound = true + return true + } + } + return false + }) + + if !lastFound { + t.Errorf("Failed to find value id%d after insertion", i+3) + } + } + } + + // Verify final length + expectedLen := 1003 // 3 initial + 1000 added + if l.Len() != expectedLen { + t.Errorf("Expected length %d, got %d", expectedLen, l.Len()) + } + + if short { + t.Skip("skipping extended recursive safety test in short mode") + } +} + // Helper function to compare slices func sliceEqual(a, b []interface{}) bool { if len(a) != len(b) { diff --git a/examples/gno.land/p/moul/collection/collection.gno b/examples/gno.land/p/moul/collection/collection.gno new file mode 100644 index 00000000000..f6d26e6a3ee --- /dev/null +++ b/examples/gno.land/p/moul/collection/collection.gno @@ -0,0 +1,509 @@ +// Package collection provides a generic collection implementation with support for +// multiple indexes, including unique indexes and case-insensitive indexes. +// It is designed to be used with any type and allows efficient lookups using +// different fields or computed values. +// +// Example usage: +// +// // Define a data type +// type User struct { +// Name string +// Email string +// Age int +// Username string +// Tags []string +// } +// +// // Create a new collection +// c := collection.New() +// +// // Add indexes with different options +// c.AddIndex("name", func(v interface{}) string { +// return v.(*User).Name +// }, UniqueIndex) +// +// c.AddIndex("email", func(v interface{}) string { +// return v.(*User).Email +// }, UniqueIndex|CaseInsensitiveIndex) +// +// c.AddIndex("age", func(v interface{}) string { +// return strconv.Itoa(v.(*User).Age) +// }, DefaultIndex) // Non-unique index +// +// c.AddIndex("username", func(v interface{}) string { +// return v.(*User).Username +// }, UniqueIndex|SparseIndex) // Allow empty usernames +// +// // For tags, we index all tags for the user +// c.AddIndex("tag", func(v interface{}) []string { +// return v.(*User).Tags +// }, DefaultIndex) // Non-unique to allow multiple users with same tag +// +// // Store an object +// id := c.Set(&User{ +// Name: "Alice", +// Email: "alice@example.com", +// Age: 30, +// Tags: []string{"admin", "moderator"}, // User can have multiple tags +// }) +// +// // Retrieve by any index +// entry := c.GetFirst("email", "alice@example.com") +// adminUsers := c.GetAll("tag", "admin") // Find all users with admin tag +// modUsers := c.GetAll("tag", "moderator") // Find all users with moderator tag +// +// Index options can be combined using the bitwise OR operator. +// Available options: +// - DefaultIndex: Regular index with no special behavior +// - UniqueIndex: Ensures values are unique within the index +// - CaseInsensitiveIndex: Makes string comparisons case-insensitive +// - SparseIndex: Skips indexing empty values (nil or empty string) +// +// Example: UniqueIndex|CaseInsensitiveIndex for a case-insensitive unique index +package collection + +import ( + "errors" + "strings" + + "gno.land/p/demo/avl" + "gno.land/p/demo/seqid" +) + +// New creates a new Collection instance with an initialized ID index. +// The ID index is a special unique index that is always present and +// serves as the primary key for all objects in the collection. +func New() *Collection { + c := &Collection{ + indexes: make(map[string]*Index), + idGen: seqid.ID(0), + } + // Initialize _id index + c.indexes[IDIndex] = &Index{ + options: UniqueIndex, + tree: avl.NewTree(), + } + return c +} + +// Collection represents a collection of objects with multiple indexes +type Collection struct { + indexes map[string]*Index + idGen seqid.ID +} + +const ( + // IDIndex is the reserved name for the primary key index + IDIndex = "_id" +) + +// IndexOption represents configuration options for an index using bit flags +type IndexOption uint64 + +const ( + // DefaultIndex is a basic index with no special options + DefaultIndex IndexOption = 0 + + // UniqueIndex ensures no duplicate values are allowed + UniqueIndex IndexOption = 1 << iota + + // CaseInsensitiveIndex automatically converts string values to lowercase + CaseInsensitiveIndex + + // SparseIndex only indexes non-empty values + SparseIndex +) + +// Index represents an index with its configuration and data. +// The index function can return either: +// - string: for single-value indexes +// - []string: for multi-value indexes where one object can be indexed under multiple keys +// +// The backing tree stores either a single ID or []string for multiple IDs per key. +type Index struct { + fn interface{} + options IndexOption + tree avl.ITree +} + +// AddIndex adds a new index to the collection with the specified options +// +// Parameters: +// - name: the unique name of the index (e.g., "tags") +// - indexFn: a function that extracts either a string or []string from an object +// - options: bit flags for index configuration (e.g., UniqueIndex) +func (c *Collection) AddIndex(name string, indexFn interface{}, options IndexOption) { + if name == IDIndex { + panic("_id is a reserved index name") + } + c.indexes[name] = &Index{ + fn: indexFn, + options: options, + tree: avl.NewTree(), + } +} + +// storeIndex handles how we store an ID in the index tree +func (idx *Index) store(key string, idStr string) { + stored, exists := idx.tree.Get(key) + if !exists { + // First entry for this key + idx.tree.Set(key, idStr) + return + } + + // Handle existing entries + switch existing := stored.(type) { + case string: + if existing == idStr { + return // Already stored + } + // Convert to array + idx.tree.Set(key, []string{existing, idStr}) + case []string: + // Check if ID already exists + for _, id := range existing { + if id == idStr { + return + } + } + // Append new ID + idx.tree.Set(key, append(existing, idStr)) + } +} + +// removeIndex handles how we remove an ID from the index tree +func (idx *Index) remove(key string, idStr string) { + stored, exists := idx.tree.Get(key) + if !exists { + return + } + + switch existing := stored.(type) { + case string: + if existing == idStr { + idx.tree.Remove(key) + } + case []string: + newIds := make([]string, 0, len(existing)) + for _, id := range existing { + if id != idStr { + newIds = append(newIds, id) + } + } + if len(newIds) == 0 { + idx.tree.Remove(key) + } else if len(newIds) == 1 { + idx.tree.Set(key, newIds[0]) + } else { + idx.tree.Set(key, newIds) + } + } +} + +// generateKeys extracts one or more keys from an object for a given index. +func generateKeys(idx *Index, obj interface{}) ([]string, bool) { + if obj == nil { + return nil, false + } + + switch fnTyped := idx.fn.(type) { + case func(interface{}) string: + // Single-value index + key := fnTyped(obj) + return []string{key}, true + case func(interface{}) []string: + // Multi-value index + keys := fnTyped(obj) + return keys, true + default: + panic("invalid index function type") + } +} + +// Set adds or updates an object in the collection. +// Returns a positive ID if successful. +// Returns 0 if: +// - The object is nil +// - A uniqueness constraint would be violated +// - Index generation fails for any index +func (c *Collection) Set(obj interface{}) uint64 { + if obj == nil { + return 0 + } + + // Generate new ID + id := c.idGen.Next() + idStr := id.String() + + // Check uniqueness constraints first + for name, idx := range c.indexes { + if name == IDIndex { + continue + } + keys, ok := generateKeys(idx, obj) + if !ok { + return 0 + } + + for _, key := range keys { + // Skip empty values for sparse indexes + if idx.options&SparseIndex != 0 && key == "" { + continue + } + if idx.options&CaseInsensitiveIndex != 0 { + key = strings.ToLower(key) + } + // Only check uniqueness for unique + single-value indexes + // (UniqueIndex is ambiguous; skipping that scenario) + if idx.options&UniqueIndex != 0 { + if existing, exists := idx.tree.Get(key); exists && existing != nil { + return 0 + } + } + } + } + + // Store in _id index first (the actual object) + c.indexes[IDIndex].tree.Set(idStr, obj) + + // Store in all other indexes + for name, idx := range c.indexes { + if name == IDIndex { + continue + } + keys, ok := generateKeys(idx, obj) + if !ok { + // Rollback: remove from _id index + c.indexes[IDIndex].tree.Remove(idStr) + return 0 + } + + for _, key := range keys { + if idx.options&SparseIndex != 0 && key == "" { + continue + } + if idx.options&CaseInsensitiveIndex != 0 { + key = strings.ToLower(key) + } + idx.store(key, idStr) + } + } + + return uint64(id) +} + +// Get retrieves entries matching the given key in the specified index. +// Returns an iterator over the matching entries. +func (c *Collection) Get(indexName string, key string) EntryIterator { + idx, exists := c.indexes[indexName] + if !exists { + return EntryIterator{err: errors.New("index not found: " + indexName)} + } + + if idx.options&CaseInsensitiveIndex != 0 { + key = strings.ToLower(key) + } + + if indexName == IDIndex { + // For ID index, validate the ID format first + _, err := seqid.FromString(key) + if err != nil { + return EntryIterator{err: err} + } + } + + return EntryIterator{ + collection: c, + indexName: indexName, + key: key, + } +} + +// GetFirst returns the first matching entry or nil if none found +func (c *Collection) GetFirst(indexName, key string) *Entry { + iter := c.Get(indexName, key) + if iter.Next() { + return iter.Value() + } + return nil +} + +// Delete removes an object by its ID and returns true if something was deleted +func (c *Collection) Delete(id uint64) bool { + idStr := seqid.ID(id).String() + + // Get the object first to clean up other indexes + obj, exists := c.indexes[IDIndex].tree.Get(idStr) + if !exists { + return false + } + + // Remove from all indexes + for name, idx := range c.indexes { + if name == IDIndex { + idx.tree.Remove(idStr) + continue + } + keys, ok := generateKeys(idx, obj) + if !ok { + continue + } + for _, key := range keys { + if idx.options&CaseInsensitiveIndex != 0 { + key = strings.ToLower(key) + } + idx.remove(key, idStr) + } + } + return true +} + +// Update updates an existing object and returns true if successful +// Returns true if the update was successful. +// Returns false if: +// - The object is nil +// - The ID doesn't exist +// - A uniqueness constraint would be violated +// - Index generation fails for any index +// +// If the update fails, the collection remains unchanged. +func (c *Collection) Update(id uint64, obj interface{}) bool { + if obj == nil { + return false + } + idStr := seqid.ID(id).String() + oldObj, exists := c.indexes[IDIndex].tree.Get(idStr) + if !exists { + return false + } + + // Check unique constraints + for name, idx := range c.indexes { + if name == IDIndex { + continue + } + + if idx.options&UniqueIndex != 0 { + newKeys, newOk := generateKeys(idx, obj) + _, oldOk := generateKeys(idx, oldObj) + if !newOk || !oldOk { + return false + } + + for _, newKey := range newKeys { + if idx.options&CaseInsensitiveIndex != 0 { + newKey = strings.ToLower(newKey) + } + + found, _ := idx.tree.Get(newKey) + if found != nil { + if storedID, ok := found.(string); !ok || storedID != idStr { + return false + } + } + } + } + } + + // Store old index entries for potential rollback + oldEntries := make(map[string][]string) + for name, idx := range c.indexes { + if name == IDIndex { + continue + } + oldKeys, ok := generateKeys(idx, oldObj) + if !ok { + continue + } + var adjusted []string + for _, okey := range oldKeys { + if idx.options&CaseInsensitiveIndex != 0 { + okey = strings.ToLower(okey) + } + // Remove the oldObj from the index right away + idx.remove(okey, idStr) + adjusted = append(adjusted, okey) + } + oldEntries[name] = adjusted + } + + // Update the object in the _id index + c.indexes[IDIndex].tree.Set(idStr, obj) + + // Add new index entries + for name, idx := range c.indexes { + if name == IDIndex { + continue + } + newKeys, ok := generateKeys(idx, obj) + if !ok { + // Rollback: restore old object and old index entries + c.indexes[IDIndex].tree.Set(idStr, oldObj) + for idxName, keys := range oldEntries { + for _, oldKey := range keys { + c.indexes[idxName].store(oldKey, idStr) + } + } + return false + } + for _, nkey := range newKeys { + if idx.options&CaseInsensitiveIndex != 0 { + nkey = strings.ToLower(nkey) + } + idx.store(nkey, idStr) + } + } + + return true +} + +// GetAll retrieves all entries matching the given key in the specified index. +func (c *Collection) GetAll(indexName string, key string) []Entry { + idx, exists := c.indexes[indexName] + if !exists { + return nil + } + + if idx.options&CaseInsensitiveIndex != 0 { + key = strings.ToLower(key) + } + + if indexName == IDIndex { + if obj, exists := idx.tree.Get(key); exists { + return []Entry{{ID: key, Obj: obj}} + } + return nil + } + + idData, exists := idx.tree.Get(key) + if !exists { + return nil + } + + // Handle both single and multi-value cases based on the actual data type + switch stored := idData.(type) { + case []string: + result := make([]Entry, 0, len(stored)) + for _, idStr := range stored { + if obj, exists := c.indexes[IDIndex].tree.Get(idStr); exists { + result = append(result, Entry{ID: idStr, Obj: obj}) + } + } + return result + case string: + if obj, exists := c.indexes[IDIndex].tree.Get(stored); exists { + return []Entry{{ID: stored, Obj: obj}} + } + } + return nil +} + +// GetIndex returns the underlying tree for an index +func (c *Collection) GetIndex(name string) avl.ITree { + idx, exists := c.indexes[name] + if !exists { + return nil + } + return idx.tree +} diff --git a/examples/gno.land/p/moul/collection/collection_test.gno b/examples/gno.land/p/moul/collection/collection_test.gno new file mode 100644 index 00000000000..3e03d222ce8 --- /dev/null +++ b/examples/gno.land/p/moul/collection/collection_test.gno @@ -0,0 +1,987 @@ +package collection + +import ( + "errors" + "strconv" + "strings" + "testing" + + "gno.land/p/demo/seqid" + "gno.land/p/demo/ufmt" +) + +type Person struct { + Name string + Age int + Email string + Username string + Tags []string +} + +func (p Person) String() string { + return ufmt.Sprintf("name=%s age=%d email=%s username=%s tags=%s", + p.Name, p.Age, p.Email, p.Username, strings.Join(p.Tags, ",")) +} + +// TestOperation represents a single operation in a test sequence +type TestOperation struct { + op string // "set" or "update" + person *Person + id uint64 // for updates + wantID uint64 + wantErr bool +} + +// TestCase represents a complete test case with setup and operations +type TestCase struct { + name string + setupIndex func(*Collection) + operations []TestOperation +} + +func TestBasicOperations(t *testing.T) { + c := New() + + // Add indexes + c.AddIndex("name", func(v interface{}) string { + return v.(*Person).Name + }, UniqueIndex) + c.AddIndex("age", func(v interface{}) string { + return strconv.Itoa(v.(*Person).Age) + }, DefaultIndex) + + // Test basic Set and Get + p1 := &Person{Name: "Alice", Age: 30, Email: "alice@test.com"} + id1 := c.Set(p1) + if id1 == 0 { + t.Error("Failed to set first object") + } + + // Get by ID + iter := c.Get(IDIndex, seqid.ID(id1).String()) + if !iter.Next() { + t.Error("Failed to get object by ID") + } + entry := iter.Value() + if entry.Obj.(*Person).Name != "Alice" { + t.Error("Got wrong object") + } +} + +func TestUniqueConstraints(t *testing.T) { + tests := []struct { + name string + setup func(*Collection) uint64 + wantID bool + }{ + { + name: "First person", + setup: func(c *Collection) uint64 { + return c.Set(&Person{Name: "Alice"}) + }, + wantID: true, + }, + { + name: "Duplicate name", + setup: func(c *Collection) uint64 { + c.Set(&Person{Name: "Alice"}) + return c.Set(&Person{Name: "Alice"}) + }, + wantID: false, + }, + { + name: "Same age (non-unique index)", + setup: func(c *Collection) uint64 { + c.AddIndex("age", func(v interface{}) string { + return strconv.Itoa(v.(*Person).Age) + }, DefaultIndex) + c.Set(&Person{Name: "Alice", Age: 30}) + return c.Set(&Person{Name: "Bob", Age: 30}) + }, + wantID: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := New() + c.AddIndex("name", func(v interface{}) string { + return v.(*Person).Name + }, UniqueIndex) + + id := tt.setup(c) + if (id != 0) != tt.wantID { + t.Errorf("Set() got id = %v, want non-zero: %v", id, tt.wantID) + } + }) + } +} + +func TestUpdates(t *testing.T) { + c := New() + c.AddIndex("name", func(v interface{}) string { + return v.(*Person).Name + }, UniqueIndex) + c.AddIndex("username", func(v interface{}) string { + return strings.ToLower(v.(*Person).Username) + }, UniqueIndex|CaseInsensitiveIndex) + + // Initial setup + p1 := &Person{Name: "Alice", Username: "alice123"} + p2 := &Person{Name: "Bob", Username: "bob456"} + + id1 := c.Set(p1) + id2 := c.Set(p2) + + tests := []struct { + name string + id uint64 + newPerson *Person + wantRet bool + }{ + { + name: "Update to non-conflicting values", + id: id1, + newPerson: &Person{Name: "Alice2", Username: "alice1234"}, + wantRet: true, + }, + { + name: "Update to conflicting username", + id: id1, + newPerson: &Person{Name: "Alice2", Username: "bob456"}, + wantRet: false, + }, + { + name: "Update non-existent ID", + id: 99999, + newPerson: &Person{Name: "Test", Username: "test"}, + wantRet: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotID := c.Update(tt.id, tt.newPerson) + if gotID != tt.wantRet { + t.Errorf("Update() got = %v, want %v", gotID, tt.wantRet) + } + }) + } +} + +func TestDelete(t *testing.T) { + c := New() + c.AddIndex("name", func(v interface{}) string { + return v.(*Person).Name + }, UniqueIndex) + + p1 := &Person{Name: "Alice"} + id1 := c.Set(p1) + + tests := []struct { + name string + id uint64 + wantRet bool + }{ + { + name: "Delete existing object", + id: id1, + wantRet: true, + }, + { + name: "Delete non-existent object", + id: 99999, + wantRet: false, + }, + { + name: "Delete already deleted object", + id: id1, + wantRet: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotID := c.Delete(tt.id) + if gotID != tt.wantRet { + t.Errorf("Delete() got = %v, want %v", gotID, tt.wantRet) + } + }) + } +} + +func TestEdgeCases(t *testing.T) { + c := New() + c.AddIndex("name", func(v interface{}) string { + return v.(*Person).Name + }, UniqueIndex) + + tests := []struct { + name string + operation func() bool + wantPanic bool + }{ + { + name: "Set nil object", + operation: func() bool { + return c.Set(nil) != 0 + }, + wantPanic: false, + }, + { + name: "Set wrong type", + operation: func() bool { + return c.Set("not a person") != 0 + }, + wantPanic: true, + }, + { + name: "Update with nil", + operation: func() bool { + id := c.Set(&Person{Name: "Test"}) + return c.Update(id, nil) + }, + wantPanic: false, + }, + { + name: "Get with invalid index name", + operation: func() bool { + iter := c.Get("invalid_index", "key") + if iter.Empty() { + return false + } + entry := iter.Value() + if entry == nil { + return false + } + id, err := seqid.FromString(entry.ID) + if err != nil { + return false + } + return true + }, + wantPanic: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var got bool + panicked := false + + func() { + defer func() { + if r := recover(); r != nil { + panicked = true + } + }() + got = tt.operation() + }() + + if panicked != tt.wantPanic { + t.Errorf("Operation panicked = %v, want panic = %v", panicked, tt.wantPanic) + } + if !panicked && got != false { + t.Errorf("Operation returned %v, want 0", got) + } + }) + } +} + +func TestIndexTypes(t *testing.T) { + c := New() + + // Test different types of indexes + c.AddIndex("composite", func(v interface{}) string { + p := v.(*Person) + return p.Name + ":" + strconv.Itoa(p.Age) + }, UniqueIndex) + + c.AddIndex("case_insensitive", func(v interface{}) string { + return strings.ToLower(v.(*Person).Username) + }, UniqueIndex|CaseInsensitiveIndex) + + // Test composite index + p1 := &Person{Name: "Alice", Age: 30, Username: "Alice123"} + id1 := c.Set(p1) + if id1 == 0 { + t.Error("Failed to set object with composite index") + } + + // Test case-insensitive index + p2 := &Person{Name: "Bob", Age: 25, Username: "alice123"} + id2 := c.Set(p2) + if id2 != 0 { + t.Error("Case-insensitive index failed to prevent duplicate") + } +} + +func TestIndexOptions(t *testing.T) { + tests := []struct { + name string + setup func(*Collection) uint64 + wantID bool + wantErr bool + }{ + { + name: "Unique case-sensitive index", + setup: func(c *Collection) uint64 { + c.AddIndex("username", func(v interface{}) string { + return v.(*Person).Username + }, UniqueIndex) + + id1 := c.Set(&Person{Username: "Alice"}) + return c.Set(&Person{Username: "Alice"}) // Should fail + }, + wantID: false, + }, + { + name: "Unique case-insensitive index", + setup: func(c *Collection) uint64 { + c.AddIndex("email", func(v interface{}) string { + return v.(*Person).Email + }, UniqueIndex|CaseInsensitiveIndex) + + id1 := c.Set(&Person{Email: "test@example.com"}) + return c.Set(&Person{Email: "TEST@EXAMPLE.COM"}) // Should fail + }, + wantID: false, + }, + { + name: "Default index", + setup: func(c *Collection) uint64 { + c.AddIndex("age", func(v interface{}) string { + return strconv.Itoa(v.(*Person).Age) + }, DefaultIndex) + + // First person with age 30 + id1 := c.Set(&Person{Age: 30}) + if id1 == 0 { + t.Error("Failed to set first person") + } + + // Second person with same age should succeed + return c.Set(&Person{Age: 30}) + }, + wantID: true, + }, + { + name: "Multiple options", + setup: func(c *Collection) uint64 { + c.AddIndex("name", func(v interface{}) string { + return v.(*Person).Name + }, UniqueIndex|CaseInsensitiveIndex|SparseIndex) + + id1 := c.Set(&Person{Name: "Alice"}) + return c.Set(&Person{Name: "ALICE"}) // Should fail + }, + wantID: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := New() // Create new collection for each test + id := tt.setup(c) + if (id != 0) != tt.wantID { + t.Errorf("got id = %v, want non-zero: %v", id, tt.wantID) + } + }) + } +} + +func TestConcurrentOperations(t *testing.T) { + c := New() + c.AddIndex("name", func(v interface{}) string { + return v.(*Person).Name + }, UniqueIndex) + + p1 := &Person{Name: "Alice"} + id1 := c.Set(p1) + iter := c.Get("_id", seqid.ID(id1).String()) + success := c.Update(id1, &Person{Name: "Alice2"}) + + if iter.Empty() || !success { + t.Error("Concurrent operations failed") + } +} + +func TestSparseIndexBehavior(t *testing.T) { + c := New() + c.AddIndex("optional_field", func(v interface{}) string { + return v.(*Person).Username + }, SparseIndex) + + tests := []struct { + name string + person *Person + wantID bool + }{ + { + name: "Empty optional field", + person: &Person{Name: "Alice", Email: "alice@test.com"}, + wantID: true, + }, + { + name: "Populated optional field", + person: &Person{Name: "Bob", Email: "bob@test.com", Username: "bobby"}, + wantID: true, + }, + { + name: "Multiple empty fields", + person: &Person{Name: "Charlie"}, + wantID: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + id := c.Set(tt.person) + if (id != 0) != tt.wantID { + t.Errorf("Set() got id = %v, want non-zero: %v", id, tt.wantID) + } + }) + } +} + +func TestIndexKeyGeneration(t *testing.T) { + c := New() + c.AddIndex("composite", func(v interface{}) string { + p := v.(*Person) + return p.Name + ":" + strconv.Itoa(p.Age) + }, UniqueIndex) + + tests := []struct { + name string + person *Person + wantID bool + }{ + { + name: "Valid composite key", + person: &Person{Name: "Alice", Age: 30}, + wantID: true, + }, + { + name: "Duplicate composite key", + person: &Person{Name: "Alice", Age: 30}, + wantID: false, + }, + { + name: "Different composite key", + person: &Person{Name: "Alice", Age: 31}, + wantID: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + id := c.Set(tt.person) + if (id != 0) != tt.wantID { + t.Errorf("Set() got id = %v, want non-zero: %v", id, tt.wantID) + } + }) + } +} + +func TestGetIndex(t *testing.T) { + c := New() + c.AddIndex("name", func(v interface{}) string { + return v.(*Person).Name + }, UniqueIndex) + + tests := []struct { + name string + indexName string + wantNil bool + }{ + { + name: "Get existing index", + indexName: "name", + wantNil: false, + }, + { + name: "Get _id index", + indexName: IDIndex, + wantNil: false, + }, + { + name: "Get non-existent index", + indexName: "invalid", + wantNil: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tree := c.GetIndex(tt.indexName) + if (tree == nil) != tt.wantNil { + t.Errorf("GetIndex() got nil = %v, want nil = %v", tree == nil, tt.wantNil) + } + }) + } +} + +func TestAddIndexPanic(t *testing.T) { + c := New() + defer func() { + if r := recover(); r == nil { + t.Error("Expected panic when adding _id index") + } + }() + + c.AddIndex(IDIndex, func(v interface{}) string { + return "" + }, DefaultIndex) +} + +func TestCaseInsensitiveOperations(t *testing.T) { + c := New() + c.AddIndex("email", func(v interface{}) string { + return v.(*Person).Email + }, UniqueIndex|CaseInsensitiveIndex) + + p := &Person{Email: "Test@Example.com"} + id := c.Set(p) + + tests := []struct { + name string + key string + wantObj bool + operation string // "get" or "getAll" + wantCount int + }{ + {"Get exact match", "Test@Example.com", true, "get", 1}, + {"Get different case", "test@example.COM", true, "get", 1}, + {"Get non-existent", "other@example.com", false, "get", 0}, + {"GetAll exact match", "Test@Example.com", true, "getAll", 1}, + {"GetAll different case", "test@example.COM", true, "getAll", 1}, + {"GetAll non-existent", "other@example.com", false, "getAll", 0}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.operation == "get" { + iter := c.Get("email", tt.key) + if iter.Empty() { + if tt.wantObj { + t.Error("Expected iterator to not be empty") + } + return + } + hasValue := iter.Next() + if hasValue != tt.wantObj { + t.Errorf("Get() got object = %v, want object = %v", hasValue, tt.wantObj) + } + if hasValue { + entry := iter.Value() + if entry.ID != seqid.ID(id).String() { + t.Errorf("Get() got id = %v, want id = %v", entry.ID, seqid.ID(id).String()) + } + } + } else { + entries := c.GetAll("email", tt.key) + if len(entries) != tt.wantCount { + t.Errorf("GetAll() returned %d entries, want %d", len(entries), tt.wantCount) + } + if tt.wantCount > 0 { + entry := entries[0] + if entry.ID != seqid.ID(id).String() { + t.Errorf("GetAll() returned ID %s, want %s", entry.ID, seqid.ID(id).String()) + } + } + } + }) + } +} + +func TestGetInvalidID(t *testing.T) { + c := New() + iter := c.Get(IDIndex, "not-a-valid-id") + if !iter.Empty() { + t.Errorf("Get() with invalid ID format got an entry, want nil") + } +} + +func TestGetAll(t *testing.T) { + c := New() + c.AddIndex("tags", func(v interface{}) []string { + return v.(*Person).Tags + }, DefaultIndex) + c.AddIndex("age", func(v interface{}) string { + return strconv.Itoa(v.(*Person).Age) + }, DefaultIndex) + c.AddIndex("name", func(v interface{}) string { + return v.(*Person).Name + }, UniqueIndex) + + // Create test data + people := []*Person{ + {Name: "Alice", Age: 30, Tags: []string{"dev", "go"}}, + {Name: "Bob", Age: 30, Tags: []string{"dev", "python"}}, + {Name: "Charlie", Age: 25, Tags: []string{"dev", "rust"}}, + } + + ids := make([]uint64, len(people)) + for i, p := range people { + ids[i] = c.Set(p) + if ids[i] == 0 { + t.Fatalf("Failed to set person %s", p.Name) + } + } + + tests := []struct { + name string + indexName string + key string + wantCount int + }{ + { + name: "Multi-value index with multiple matches", + indexName: "tags", + key: "dev", + wantCount: 3, + }, + { + name: "Multi-value index with single match", + indexName: "tags", + key: "go", + wantCount: 1, + }, + { + name: "Multi-value index with no matches", + indexName: "tags", + key: "java", + wantCount: 0, + }, + { + name: "Single-value non-unique index with multiple matches", + indexName: "age", + key: "30", + wantCount: 2, + }, + { + name: "Single-value unique index", + indexName: "name", + key: "Alice", + wantCount: 1, + }, + { + name: "Non-existent index", + indexName: "invalid", + key: "value", + wantCount: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + iter := c.Get(tt.indexName, tt.key) + count := 0 + for iter.Next() { + entry := iter.Value() + if entry.ID == "" { + t.Error("Got entry with empty ID") + } + if entry.Obj == nil { + t.Error("Got entry with nil Obj") + } + count++ + } + if count != tt.wantCount { + t.Errorf("Got %d entries, want %d", count, tt.wantCount) + } + }) + } +} + +func TestIndexOperations(t *testing.T) { + tests := []struct { + name string + setup func(*Collection) (uint64, error) + verify func(*Collection, uint64) error + wantErr bool + }{ + { + name: "Basic set and get", + setup: func(c *Collection) (uint64, error) { + c.AddIndex("name", func(v interface{}) string { + return v.(*Person).Name + }, UniqueIndex) + return c.Set(&Person{Name: "Alice", Age: 30}), nil + }, + verify: func(c *Collection, id uint64) error { + iter := c.Get(IDIndex, seqid.ID(id).String()) + if !iter.Next() { + return errors.New("failed to get object by ID") + } + entry := iter.Value() + if entry.Obj.(*Person).Name != "Alice" { + return errors.New("got wrong object") + } + return nil + }, + }, + { + name: "Composite index", + setup: func(c *Collection) (uint64, error) { + c.AddIndex("composite", func(v interface{}) string { + p := v.(*Person) + return p.Name + ":" + strconv.Itoa(p.Age) + }, UniqueIndex) + return c.Set(&Person{Name: "Alice", Age: 30}), nil + }, + verify: func(c *Collection, id uint64) error { + iter := c.Get("composite", "Alice:30") + if !iter.Next() { + return errors.New("failed to get object by composite index") + } + return nil + }, + }, + // Add more test cases combining unique scenarios from original tests + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := New() + id, err := tt.setup(c) + if (err != nil) != tt.wantErr { + t.Errorf("setup error = %v, wantErr %v", err, tt.wantErr) + return + } + if err == nil { + if err := tt.verify(c, id); err != nil { + t.Errorf("verification failed: %v", err) + } + } + }) + } +} + +func TestMultiValueIndexes(t *testing.T) { + c := New() + c.AddIndex("tags", func(v interface{}) []string { + return v.(*Person).Tags + }, DefaultIndex) + + tests := []struct { + name string + setup []*Person + searchTag string + wantCount int + }{ + { + name: "Multiple tags, multiple matches", + setup: []*Person{ + {Name: "Alice", Tags: []string{"dev", "go"}}, + {Name: "Bob", Tags: []string{"dev", "python"}}, + {Name: "Charlie", Tags: []string{"dev", "rust"}}, + }, + searchTag: "dev", + wantCount: 3, + }, + { + name: "Single tag match", + setup: []*Person{ + {Name: "Alice", Tags: []string{"dev", "go"}}, + {Name: "Bob", Tags: []string{"dev", "python"}}, + }, + searchTag: "go", + wantCount: 1, + }, + { + name: "No matches", + setup: []*Person{ + {Name: "Alice", Tags: []string{"dev", "go"}}, + {Name: "Bob", Tags: []string{"dev", "python"}}, + }, + searchTag: "java", + wantCount: 0, + }, + { + name: "Empty tags", + setup: []*Person{ + {Name: "Alice", Tags: []string{}}, + {Name: "Bob", Tags: nil}, + }, + searchTag: "dev", + wantCount: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := New() + c.AddIndex("tags", func(v interface{}) []string { + return v.(*Person).Tags + }, DefaultIndex) + + // Setup test data + for _, p := range tt.setup { + if id := c.Set(p); id == 0 { + t.Fatalf("Failed to set person %s", p.Name) + } + } + + // Test Get operation + iter := c.Get("tags", tt.searchTag) + count := 0 + for iter.Next() { + count++ + } + if count != tt.wantCount { + t.Errorf("Get() got %d matches, want %d", count, tt.wantCount) + } + }) + } +} + +func TestGetOperations(t *testing.T) { + c := New() + c.AddIndex("name", func(v interface{}) string { + return v.(*Person).Name + }, UniqueIndex) + c.AddIndex("age", func(v interface{}) string { + return strconv.Itoa(v.(*Person).Age) + }, DefaultIndex) + + // Setup test data + testPeople := []*Person{ + {Name: "Alice", Age: 30}, + {Name: "Bob", Age: 30}, + {Name: "Charlie", Age: 25}, + } + + ids := make([]uint64, len(testPeople)) + for i, p := range testPeople { + ids[i] = c.Set(p) + if ids[i] == 0 { + t.Fatalf("Failed to set person %s", p.Name) + } + } + + tests := []struct { + name string + indexName string + key string + wantCount int + wantErr bool + }{ + { + name: "Get by ID", + indexName: IDIndex, + key: seqid.ID(ids[0]).String(), + wantCount: 1, + wantErr: false, + }, + { + name: "Get by unique index", + indexName: "name", + key: "Alice", + wantCount: 1, + wantErr: false, + }, + { + name: "Get by non-unique index", + indexName: "age", + key: "30", + wantCount: 2, + wantErr: false, + }, + { + name: "Get with invalid index", + indexName: "invalid_index", + key: "value", + wantCount: 0, + wantErr: true, + }, + { + name: "Get with invalid ID format", + indexName: IDIndex, + key: "not-a-valid-id", + wantCount: 0, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + iter := c.Get(tt.indexName, tt.key) + if iter.Empty() { + if !tt.wantErr { + t.Errorf("Get() returned empty iterator, wanted %d results", tt.wantCount) + } + return + } + + count := 0 + for iter.Next() { + entry := iter.Value() + if entry.ID == "" { + t.Error("Got entry with empty ID") + } + if entry.Obj == nil { + t.Error("Got entry with nil Obj") + } + count++ + } + + if count != tt.wantCount { + t.Errorf("Get() returned %d results, want %d", count, tt.wantCount) + } + }) + } +} + +func TestEntryString(t *testing.T) { + tests := []struct { + name string + entry *Entry + expected string + }{ + { + name: "Nil entry", + entry: nil, + expected: "", + }, + { + name: "Person entry", + entry: &Entry{ + ID: "123", + Obj: &Person{Name: "Alice", Age: 30}, + }, + expected: `Entry{ID: 123, Obj: name=Alice age=30 email= username= tags=}`, + }, + { + name: "Entry with nil object", + entry: &Entry{ + ID: "456", + Obj: nil, + }, + expected: `Entry{ID: 456, Obj: }`, + }, + { + name: "Entry with complete person", + entry: &Entry{ + ID: "789", + Obj: &Person{ + Name: "Bob", + Age: 25, + Email: "bob@example.com", + Username: "bobby", + Tags: []string{"dev", "go"}, + }, + }, + expected: `Entry{ID: 789, Obj: name=Bob age=25 email=bob@example.com username=bobby tags=dev,go}`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.entry.String() + if got != tt.expected { + t.Errorf("Entry.String() = %q, want %q", got, tt.expected) + } + }) + } +} diff --git a/examples/gno.land/p/moul/collection/entry.gno b/examples/gno.land/p/moul/collection/entry.gno new file mode 100644 index 00000000000..8daa893b61d --- /dev/null +++ b/examples/gno.land/p/moul/collection/entry.gno @@ -0,0 +1,149 @@ +package collection + +import "gno.land/p/demo/ufmt" + +// Entry represents a single object in the collection with its ID +type Entry struct { + ID string + Obj interface{} +} + +// String returns a string representation of the Entry +func (e *Entry) String() string { + if e == nil { + return "" + } + return ufmt.Sprintf("Entry{ID: %s, Obj: %v}", e.ID, e.Obj) +} + +// EntryIterator provides iteration over collection entries +type EntryIterator struct { + collection *Collection + indexName string + key string + currentID string + currentObj interface{} + err error + closed bool + + // For multi-value cases + ids []string + currentIdx int +} + +func (ei *EntryIterator) Close() error { + ei.closed = true + ei.currentID = "" + ei.currentObj = nil + ei.ids = nil + return nil +} + +func (ei *EntryIterator) Next() bool { + if ei == nil || ei.closed || ei.err != nil { + return false + } + + // Handle ID index specially + if ei.indexName == IDIndex { + if ei.currentID != "" { // We've already returned the single value + return false + } + obj, exists := ei.collection.indexes[IDIndex].tree.Get(ei.key) + if !exists { + return false + } + ei.currentID = ei.key + ei.currentObj = obj + return true + } + + // Get the index + idx, exists := ei.collection.indexes[ei.indexName] + if !exists { + return false + } + + // Initialize ids slice if needed + if ei.ids == nil { + idData, exists := idx.tree.Get(ei.key) + if !exists { + return false + } + + switch stored := idData.(type) { + case []string: + ei.ids = stored + ei.currentIdx = -1 + case string: + ei.ids = []string{stored} + ei.currentIdx = -1 + default: + return false + } + } + + // Move to next ID + ei.currentIdx++ + if ei.currentIdx >= len(ei.ids) { + return false + } + + // Fetch the actual object + ei.currentID = ei.ids[ei.currentIdx] + obj, exists := ei.collection.indexes[IDIndex].tree.Get(ei.currentID) + if !exists { + // Skip invalid entries + return ei.Next() + } + ei.currentObj = obj + return true +} + +func (ei *EntryIterator) Error() error { + return ei.err +} + +func (ei *EntryIterator) Value() *Entry { + if ei == nil || ei.closed || ei.currentID == "" { + return nil + } + return &Entry{ + ID: ei.currentID, + Obj: ei.currentObj, + } +} + +func (ei *EntryIterator) Empty() bool { + if ei == nil || ei.closed || ei.err != nil { + return true + } + + // Handle ID index specially + if ei.indexName == IDIndex { + _, exists := ei.collection.indexes[IDIndex].tree.Get(ei.key) + return !exists + } + + // Get the index + idx, exists := ei.collection.indexes[ei.indexName] + if !exists { + return true + } + + // Check if key exists in index + idData, exists := idx.tree.Get(ei.key) + if !exists { + return true + } + + // Check if there are any valid IDs + switch stored := idData.(type) { + case []string: + return len(stored) == 0 + case string: + return stored == "" + default: + return true + } +} diff --git a/examples/gno.land/p/moul/collection/gno.mod b/examples/gno.land/p/moul/collection/gno.mod new file mode 100644 index 00000000000..a6eeca36837 --- /dev/null +++ b/examples/gno.land/p/moul/collection/gno.mod @@ -0,0 +1 @@ +module gno.land/p/moul/collection From 3fa9940b85a9eeac64475cec4d5e10877c6dc14a Mon Sep 17 00:00:00 2001 From: Dmitry <98899785+mdqst@users.noreply.github.com> Date: Wed, 15 Jan 2025 22:15:03 +0300 Subject: [PATCH 18/20] chore(autocounterd): fix `ShortHelp`, improve error messages (#3504) Per title. --- misc/autocounterd/cmd/cmd_start.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/misc/autocounterd/cmd/cmd_start.go b/misc/autocounterd/cmd/cmd_start.go index ecf70f750be..e5155bf0718 100644 --- a/misc/autocounterd/cmd/cmd_start.go +++ b/misc/autocounterd/cmd/cmd_start.go @@ -44,7 +44,7 @@ func NewStartCmd(io commands.IO) *commands.Command { commands.Metadata{ Name: "start", ShortUsage: "start [flags]", - ShortHelp: "Runs the linter for the specified packages", + ShortHelp: "Increments the counter in the specified realm at regular intervals", }, cfg, func(_ context.Context, args []string) error { @@ -68,15 +68,15 @@ func execStart(cfg *startCfg, args []string, io commands.IO) error { signer, err := gnoclient.SignerFromBip39(cfg.mnemonic, cfg.chainID, "", uint32(0), uint32(0)) if err != nil { - return err + return fmt.Errorf("failed to create signer: %w", err) } if err := signer.Validate(); err != nil { - return err + return fmt.Errorf("invalid signer: %w", err) } rpcClient, err := rpcclient.NewHTTPClient(cfg.rpcURL) if err != nil { - return err + return fmt.Errorf("failed to create RPC client: %w", err) } client := gnoclient.Client{ @@ -97,7 +97,7 @@ func execStart(cfg *startCfg, args []string, io commands.IO) error { }) if err != nil { - fmt.Printf("[ERROR] Failed to call Incr on %s, %+v\n", cfg.realmPath, err.Error()) + fmt.Printf("[ERROR] Failed to call Incr on %s: %v\n", cfg.realmPath, err) } else { fmt.Println("[INFO] Counter incremented with success") } From cb2255f61c0abd0144550df223182334c6d50c51 Mon Sep 17 00:00:00 2001 From: Alexis Colin Date: Thu, 16 Jan 2025 17:10:39 +0900 Subject: [PATCH 19/20] fix(gnoweb): enable table on gnoweb (#3525) --- gno.land/pkg/gnoweb/app.go | 2 ++ gno.land/pkg/gnoweb/frontend/css/input.css | 14 ++------------ gno.land/pkg/gnoweb/public/styles.css | 2 +- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/gno.land/pkg/gnoweb/app.go b/gno.land/pkg/gnoweb/app.go index 1deea86644b..e99aa7ea056 100644 --- a/gno.land/pkg/gnoweb/app.go +++ b/gno.land/pkg/gnoweb/app.go @@ -15,6 +15,7 @@ import ( "github.com/gnolang/gno/gno.land/pkg/gnoweb/components" "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" "github.com/yuin/goldmark" + "github.com/yuin/goldmark/extension" mdhtml "github.com/yuin/goldmark/renderer/html" ) @@ -77,6 +78,7 @@ func NewRouter(logger *slog.Logger, cfg *AppConfig) (http.Handler, error) { markdown.NewHighlighting( markdown.WithFormatOptions(chromaOptions...), ), + extension.Table, ), } if cfg.UnsafeHTML { diff --git a/gno.land/pkg/gnoweb/frontend/css/input.css b/gno.land/pkg/gnoweb/frontend/css/input.css index f86076dbe54..59e41ff4a7c 100644 --- a/gno.land/pkg/gnoweb/frontend/css/input.css +++ b/gno.land/pkg/gnoweb/frontend/css/input.css @@ -147,12 +147,12 @@ } .realm-content table { - @apply w-full border-collapse my-8; + @apply border-collapse my-8 block w-full max-w-full overflow-x-auto border-collapse; } .realm-content th, .realm-content td { - @apply border border-gray-300 px-4 py-2; + @apply border px-4 py-2 break-words whitespace-normal; } .realm-content th { @@ -190,16 +190,6 @@ @apply list-decimal; } - .realm-content table th:first-child, - .realm-content td:first-child { - @apply pl-0; - } - - .realm-content table th:last-child, - .realm-content td:last-child { - @apply pr-0; - } - .realm-content abbr[title] { @apply border-b border-dotted cursor-help; } diff --git a/gno.land/pkg/gnoweb/public/styles.css b/gno.land/pkg/gnoweb/public/styles.css index 4ff1a266c0c..8e8d7ed802d 100644 --- a/gno.land/pkg/gnoweb/public/styles.css +++ b/gno.land/pkg/gnoweb/public/styles.css @@ -1,3 +1,3 @@ @font-face{font-family:Roboto;font-style:normal;font-weight:900;font-display:swap;src:url(fonts/roboto/roboto-mono-normal.woff2) format("woff2"),url(fonts/roboto/roboto-mono-normal.woff) format("woff")}@font-face{font-family:Inter var;font-weight:100 900;font-display:block;font-style:oblique 0deg 10deg;src:url(fonts/intervar/Intervar.woff2) format("woff2")}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: } -/*! tailwindcss v3.4.14 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #bdbdbd}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#7c7c7c}input::placeholder,textarea::placeholder{opacity:1;color:#7c7c7c}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}html{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));font-family:Inter var,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji,sans-serif;font-size:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;-moz-osx-font-smoothing:grayscale;font-smoothing:antialiased;font-variant-ligatures:contextual common-ligatures;font-kerning:normal;text-rendering:optimizeLegibility}svg{max-height:100%;max-width:100%}form{margin-top:0;margin-bottom:0}.realm-content{overflow-wrap:break-word;padding-top:2.5rem;font-size:1rem}.realm-content>:first-child{margin-top:0!important}.realm-content a{font-weight:500;--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.realm-content a:hover{text-decoration-line:underline}.realm-content h1,.realm-content h2,.realm-content h3,.realm-content h4{margin-top:3rem;line-height:1.25;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-content h2,.realm-content h2 *{font-weight:700}.realm-content h3,.realm-content h3 *,.realm-content h4,.realm-content h4 *{font-weight:600}.realm-content h1+h2,.realm-content h2+h3,.realm-content h3+h4{margin-top:1rem}.realm-content h1{font-size:2.375rem;font-weight:700}.realm-content h2{font-size:1.5rem}.realm-content h3{margin-top:2.5rem;font-size:1.25rem}.realm-content h3,.realm-content h4{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content h4{margin-top:1.5rem;margin-bottom:1.5rem;font-size:1.125rem;font-weight:500}.realm-content p{margin-top:1.25rem;margin-bottom:1.25rem}.realm-content strong{font-weight:700;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-content strong *{font-weight:700}.realm-content em{font-style:oblique 10deg}.realm-content blockquote{margin-top:1rem;margin-bottom:1rem;border-left-width:4px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-style:oblique 10deg}.realm-content ol,.realm-content ul{margin-top:1.5rem;margin-bottom:1.5rem;padding-left:1rem}.realm-content ol li,.realm-content ul li{margin-bottom:.5rem}.realm-content img{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-content figure{margin-top:1.5rem;margin-bottom:1.5rem;text-align:center}.realm-content figcaption{font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content :not(pre)>code{border-radius:.25rem;background-color:rgb(226 226 226/var(--tw-bg-opacity));padding:.125rem .25rem;font-size:.96em}.realm-content :not(pre)>code,.realm-content pre{--tw-bg-opacity:1;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-content pre{overflow-x:auto;border-radius:.375rem;background-color:rgb(240 240 240/var(--tw-bg-opacity));padding:1rem}.realm-content hr{margin-top:2.5rem;margin-bottom:2.5rem;border-top-width:1px;--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.realm-content table{margin-top:2rem;margin-bottom:2rem;width:100%;border-collapse:collapse}.realm-content td,.realm-content th{border-width:1px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding:.5rem 1rem}.realm-content th{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));font-weight:700}.realm-content caption{margin-top:.5rem;text-align:left;font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content q{margin-top:1.5rem;margin-bottom:1.5rem;border-left-width:4px;--tw-border-opacity:1;border-left-color:rgb(204 204 204/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(85 85 85/var(--tw-text-opacity));font-style:oblique 10deg;quotes:"“" "”" "‘" "’"}.realm-content q:after,.realm-content q:before{margin-right:.25rem;font-size:1.5rem;--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity));content:open-quote;vertical-align:-.4rem}.realm-content q:after{content:close-quote}.realm-content q:before{content:open-quote}.realm-content q:after{content:close-quote}.realm-content ol ol,.realm-content ol ul,.realm-content ul ol,.realm-content ul ul{margin-top:.75rem;margin-bottom:.5rem;padding-left:1rem}.realm-content ul{list-style-type:disc}.realm-content ol{list-style-type:decimal}.realm-content table th:first-child,.realm-content td:first-child{padding-left:0}.realm-content table th:last-child,.realm-content td:last-child{padding-right:0}.realm-content abbr[title]{cursor:help;border-bottom-width:1px;border-style:dotted}.realm-content details{margin-top:1.25rem;margin-bottom:1.25rem}.realm-content summary{cursor:pointer;font-weight:700}.realm-content a code{color:inherit}.realm-content video{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-content math{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-content small{font-size:.875rem}.realm-content del{text-decoration-line:line-through}.realm-content sub{vertical-align:sub;font-size:.75rem}.realm-content sup{vertical-align:super;font-size:.75rem}.realm-content button,.realm-content input{border-width:1px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding:.5rem 1rem}main :is(h1,h2,h3,h4){scroll-margin-top:6rem}::-moz-selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}::selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.sidemenu .peer:checked+label>svg{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.toc-expend-btn:has(#toc-expend:checked)+nav{display:block}.toc-expend-btn:has(#toc-expend:checked) .toc-expend-btn_ico{--tw-rotate:180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.main-header:has(#sidemenu-docs:checked)+main #sidebar #sidebar-docs,.main-header:has(#sidemenu-meta:checked)+main #sidebar #sidebar-meta,.main-header:has(#sidemenu-source:checked)+main #sidebar #sidebar-source,.main-header:has(#sidemenu-summary:checked)+main #sidebar #sidebar-summary{display:block}@media (min-width:40rem){:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .main-navigation,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main .realm-content{grid-column:span 6/span 6}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .sidemenu,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar{grid-column:span 4/span 4}}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar:before{position:absolute;top:0;left:-1.75rem;z-index:-1;display:block;height:100%;width:50vw;--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));--tw-content:"";content:var(--tw-content)}main :is(.source-code)>pre{overflow:scroll;border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(255 255 255/var(--tw-bg-opacity))!important;padding:1rem .25rem;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-size:.875rem}@media (min-width:40rem){main :is(.source-code)>pre{padding:2rem .75rem;font-size:1rem}}main .realm-content>pre a:hover{text-decoration-line:none}main :is(.realm-content,.source-code)>pre .chroma-ln:target{background-color:transparent!important}main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-ln:target),main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-ln:target) .chroma-cl,main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover),main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover) .chroma-cl{border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(226 226 226/var(--tw-bg-opacity))!important}main :is(.realm-content,.source-code)>pre .chroma-ln{scroll-margin-top:6rem}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.bottom-1{bottom:.25rem}.left-0{left:0}.right-2{right:.5rem}.right-3{right:.75rem}.top-0{top:0}.top-1\/2{top:50%}.top-14{top:3.5rem}.top-2{top:.5rem}.z-1{z-index:1}.z-max{z-index:9999}.col-span-1{grid-column:span 1/span 1}.col-span-10{grid-column:span 10/span 10}.col-span-3{grid-column:span 3/span 3}.col-span-7{grid-column:span 7/span 7}.row-span-1{grid-row:span 1/span 1}.row-start-1{grid-row-start:1}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-8{margin-bottom:2rem}.mr-10{margin-right:2.5rem}.mt-1{margin-top:.25rem}.mt-10{margin-top:2.5rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-full{height:100%}.max-h-screen{max-height:100vh}.min-h-full{min-height:100%}.min-h-screen{min-height:100vh}.w-10{width:2.5rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-full{width:100%}.min-w-2{min-width:.5rem}.min-w-48{min-width:12rem}.max-w-screen-max{max-width:98.75rem}.shrink-0{flex-shrink:0}.grow-\[2\]{flex-grow:2}.-translate-y-1\/2{--tw-translate-y:-50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.list-none{list-style-type:none}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-flow-dense{grid-auto-flow:dense}.auto-rows-min{grid-auto-rows:min-content}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-8{gap:2rem}.gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-y-2{row-gap:.5rem}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.rounded{border-radius:.375rem}.rounded-sm{border-radius:.25rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-t{border-top-width:1px}.border-gray-100{--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.bg-gray-300{--tw-bg-opacity:1;background-color:rgb(153 153 153/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.bg-light{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-transparent{background-color:transparent}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-4{padding:1rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-10{padding-left:2.5rem;padding-right:2.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-px{padding-top:1px;padding-bottom:1px}.pb-24{padding-bottom:6rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pb-6{padding-bottom:1.5rem}.pb-8{padding-bottom:2rem}.pl-4{padding-left:1rem}.pr-10{padding-right:2.5rem}.pt-0\.5{padding-top:.125rem}.pt-2{padding-top:.5rem}.font-mono{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.text-100{font-size:.875rem}.text-200{font-size:1rem}.text-50{font-size:.75rem}.text-600{font-size:1.5rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.capitalize{text-transform:capitalize}.leading-tight{line-height:1.25}.text-gray-300{--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(124 124 124/var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity:1;color:rgb(19 19 19/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.text-light{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.outline-none{outline:2px solid transparent;outline-offset:2px}.text-stroke{-webkit-text-stroke:currentColor;-webkit-text-stroke-width:.6px}.no-scrollbar::-webkit-scrollbar{display:none}.no-scrollbar{-ms-overflow-style:none;scrollbar-width:none}.\*\:pl-0>*{padding-left:0}.before\:px-\[0\.18rem\]:before{content:var(--tw-content);padding-left:.18rem;padding-right:.18rem}.before\:text-gray-300:before{content:var(--tw-content);--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.before\:content-\[\'\/\'\]:before{--tw-content:"/";content:var(--tw-content)}.before\:content-\[\'\:\'\]:before{--tw-content:":";content:var(--tw-content)}.before\:content-\[\'open\'\]:before{--tw-content:"open";content:var(--tw-content)}.after\:pointer-events-none:after{content:var(--tw-content);pointer-events:none}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:bottom-0:after{content:var(--tw-content);bottom:0}.after\:left-0:after{content:var(--tw-content);left:0}.after\:top-0:after{content:var(--tw-content);top:0}.after\:block:after{content:var(--tw-content);display:block}.after\:h-1:after{content:var(--tw-content);height:.25rem}.after\:h-full:after{content:var(--tw-content);height:100%}.after\:w-full:after{content:var(--tw-content);width:100%}.after\:rounded-t-sm:after{content:var(--tw-content);border-top-left-radius:.25rem;border-top-right-radius:.25rem}.after\:bg-gray-100:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.after\:bg-green-600:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.first\:border-t:first-child{border-top-width:1px}.hover\:border-gray-300:hover{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.hover\:bg-green-600:hover{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.hover\:text-gray-600:hover{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.hover\:text-green-600:hover{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.hover\:text-light:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.hover\:underline:hover{text-decoration-line:underline}.focus\:border-gray-300:focus{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.focus\:border-l-gray-300:focus{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-gray-300{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-l-gray-300{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group.is-active .group-\[\.is-active\]\:text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.peer:checked~.peer-checked\:before\:content-\[\'close\'\]:before{--tw-content:"close";content:var(--tw-content)}.peer:focus-within~.peer-focus-within\:hidden{display:none}.has-\[ul\:empty\]\:hidden:has(ul:empty){display:none}.has-\[\:focus-within\]\:border-gray-300:has(:focus-within){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.has-\[\:focus\]\:border-gray-300:has(:focus){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}@media (min-width:30rem){.sm\:gap-6{gap:1.5rem}}@media (min-width:40rem){.md\:col-span-3{grid-column:span 3/span 3}.md\:mb-0{margin-bottom:0}.md\:h-4{height:1rem}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:items-center{align-items:center}.md\:gap-x-8{-moz-column-gap:2rem;column-gap:2rem}.md\:px-10{padding-left:2.5rem;padding-right:2.5rem}.md\:pb-0{padding-bottom:0}}@media (min-width:51.25rem){.lg\:order-2{order:2}.lg\:col-span-3{grid-column:span 3/span 3}.lg\:col-span-7{grid-column:span 7/span 7}.lg\:row-span-2{grid-row:span 2/span 2}.lg\:row-start-1{grid-row-start:1}.lg\:row-start-2{grid-row-start:2}.lg\:mb-4{margin-bottom:1rem}.lg\:mt-0{margin-top:0}.lg\:mt-10{margin-top:2.5rem}.lg\:block{display:block}.lg\:hidden{display:none}.lg\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.lg\:flex-row{flex-direction:row}.lg\:justify-start{justify-content:flex-start}.lg\:justify-between{justify-content:space-between}.lg\:gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.lg\:border-none{border-style:none}.lg\:bg-transparent{background-color:transparent}.lg\:p-0{padding:0}.lg\:px-0{padding-left:0;padding-right:0}.lg\:px-2{padding-left:.5rem;padding-right:.5rem}.lg\:py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.lg\:pb-28{padding-bottom:7rem}.lg\:pt-2{padding-top:.5rem}.lg\:text-200{font-size:1rem}.lg\:font-semibold{font-weight:600}.lg\:hover\:bg-transparent:hover{background-color:transparent}}@media (min-width:63.75rem){.xl\:inline{display:inline}.xl\:hidden{display:none}.xl\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xl\:flex-row{flex-direction:row}.xl\:items-center{align-items:center}.xl\:gap-20{gap:5rem}.xl\:gap-6{gap:1.5rem}.xl\:pt-0{padding-top:0}}@media (min-width:85.375rem){.xxl\:inline-block{display:inline-block}.xxl\:h-4{height:1rem}.xxl\:w-4{width:1rem}.xxl\:gap-20{gap:5rem}.xxl\:gap-x-32{-moz-column-gap:8rem;column-gap:8rem}.xxl\:pr-1{padding-right:.25rem}} \ No newline at end of file +/*! tailwindcss v3.4.14 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #bdbdbd}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#7c7c7c}input::placeholder,textarea::placeholder{opacity:1;color:#7c7c7c}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}html{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));font-family:Inter var,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji,sans-serif;font-size:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-font-feature-settings:"kern" on,"liga" on,"calt" on,"zero" on;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;-moz-osx-font-smoothing:grayscale;font-smoothing:antialiased;font-variant-ligatures:contextual common-ligatures;font-kerning:normal;text-rendering:optimizeLegibility}svg{max-height:100%;max-width:100%}form{margin-top:0;margin-bottom:0}.realm-content{overflow-wrap:break-word;padding-top:2.5rem;font-size:1rem}.realm-content>:first-child{margin-top:0!important}.realm-content a{font-weight:500;--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.realm-content a:hover{text-decoration-line:underline}.realm-content h1,.realm-content h2,.realm-content h3,.realm-content h4{margin-top:3rem;line-height:1.25;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-content h2,.realm-content h2 *{font-weight:700}.realm-content h3,.realm-content h3 *,.realm-content h4,.realm-content h4 *{font-weight:600}.realm-content h1+h2,.realm-content h2+h3,.realm-content h3+h4{margin-top:1rem}.realm-content h1{font-size:2.375rem;font-weight:700}.realm-content h2{font-size:1.5rem}.realm-content h3{margin-top:2.5rem;font-size:1.25rem}.realm-content h3,.realm-content h4{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content h4{margin-top:1.5rem;margin-bottom:1.5rem;font-size:1.125rem;font-weight:500}.realm-content p{margin-top:1.25rem;margin-bottom:1.25rem}.realm-content strong{font-weight:700;--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.realm-content strong *{font-weight:700}.realm-content em{font-style:oblique 10deg}.realm-content blockquote{margin-top:1rem;margin-bottom:1rem;border-left-width:4px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity));font-style:oblique 10deg}.realm-content ol,.realm-content ul{margin-top:1.5rem;margin-bottom:1.5rem;padding-left:1rem}.realm-content ol li,.realm-content ul li{margin-bottom:.5rem}.realm-content img{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-content figure{margin-top:1.5rem;margin-bottom:1.5rem;text-align:center}.realm-content figcaption{font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content :not(pre)>code{border-radius:.25rem;background-color:rgb(226 226 226/var(--tw-bg-opacity));padding:.125rem .25rem;font-size:.96em}.realm-content :not(pre)>code,.realm-content pre{--tw-bg-opacity:1;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-content pre{overflow-x:auto;border-radius:.375rem;background-color:rgb(240 240 240/var(--tw-bg-opacity));padding:1rem}.realm-content hr{margin-top:2.5rem;margin-bottom:2.5rem;border-top-width:1px;--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.realm-content table{margin-top:2rem;margin-bottom:2rem;display:block;width:100%;max-width:100%;border-collapse:collapse;overflow-x:auto}.realm-content td,.realm-content th{white-space:normal;overflow-wrap:break-word;border-width:1px;padding:.5rem 1rem}.realm-content th{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));font-weight:700}.realm-content caption{margin-top:.5rem;text-align:left;font-size:.875rem;--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.realm-content q{margin-top:1.5rem;margin-bottom:1.5rem;border-left-width:4px;--tw-border-opacity:1;border-left-color:rgb(204 204 204/var(--tw-border-opacity));padding-left:1rem;--tw-text-opacity:1;color:rgb(85 85 85/var(--tw-text-opacity));font-style:oblique 10deg;quotes:"“" "”" "‘" "’"}.realm-content q:after,.realm-content q:before{margin-right:.25rem;font-size:1.5rem;--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity));content:open-quote;vertical-align:-.4rem}.realm-content q:after{content:close-quote}.realm-content q:before{content:open-quote}.realm-content q:after{content:close-quote}.realm-content ol ol,.realm-content ol ul,.realm-content ul ol,.realm-content ul ul{margin-top:.75rem;margin-bottom:.5rem;padding-left:1rem}.realm-content ul{list-style-type:disc}.realm-content ol{list-style-type:decimal}.realm-content abbr[title]{cursor:help;border-bottom-width:1px;border-style:dotted}.realm-content details{margin-top:1.25rem;margin-bottom:1.25rem}.realm-content summary{cursor:pointer;font-weight:700}.realm-content a code{color:inherit}.realm-content video{margin-top:2rem;margin-bottom:2rem;max-width:100%}.realm-content math{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.realm-content small{font-size:.875rem}.realm-content del{text-decoration-line:line-through}.realm-content sub{vertical-align:sub;font-size:.75rem}.realm-content sup{vertical-align:super;font-size:.75rem}.realm-content button,.realm-content input{border-width:1px;--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity));padding:.5rem 1rem}main :is(h1,h2,h3,h4){scroll-margin-top:6rem}::-moz-selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}::selection{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.sidemenu .peer:checked+label>svg{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.toc-expend-btn:has(#toc-expend:checked)+nav{display:block}.toc-expend-btn:has(#toc-expend:checked) .toc-expend-btn_ico{--tw-rotate:180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.main-header:has(#sidemenu-docs:checked)+main #sidebar #sidebar-docs,.main-header:has(#sidemenu-meta:checked)+main #sidebar #sidebar-meta,.main-header:has(#sidemenu-source:checked)+main #sidebar #sidebar-source,.main-header:has(#sidemenu-summary:checked)+main #sidebar #sidebar-summary{display:block}@media (min-width:40rem){:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .main-navigation,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main .realm-content{grid-column:span 6/span 6}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked)) .sidemenu,:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar{grid-column:span 4/span 4}}:is(.main-header:has(#sidemenu-source:checked),.main-header:has(#sidemenu-docs:checked),.main-header:has(#sidemenu-meta:checked))+main #sidebar:before{position:absolute;top:0;left:-1.75rem;z-index:-1;display:block;height:100%;width:50vw;--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity));--tw-content:"";content:var(--tw-content)}main :is(.source-code)>pre{overflow:scroll;border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(255 255 255/var(--tw-bg-opacity))!important;padding:1rem .25rem;font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;;font-size:.875rem}@media (min-width:40rem){main :is(.source-code)>pre{padding:2rem .75rem;font-size:1rem}}main .realm-content>pre a:hover{text-decoration-line:none}main :is(.realm-content,.source-code)>pre .chroma-ln:target{background-color:transparent!important}main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-ln:target),main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-ln:target) .chroma-cl,main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover),main :is(.realm-content,.source-code)>pre .chroma-line:has(.chroma-lnlinks:hover) .chroma-cl{border-radius:.375rem;--tw-bg-opacity:1!important;background-color:rgb(226 226 226/var(--tw-bg-opacity))!important}main :is(.realm-content,.source-code)>pre .chroma-ln{scroll-margin-top:6rem}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.bottom-1{bottom:.25rem}.left-0{left:0}.right-2{right:.5rem}.right-3{right:.75rem}.top-0{top:0}.top-1\/2{top:50%}.top-14{top:3.5rem}.top-2{top:.5rem}.z-1{z-index:1}.z-max{z-index:9999}.col-span-1{grid-column:span 1/span 1}.col-span-10{grid-column:span 10/span 10}.col-span-3{grid-column:span 3/span 3}.col-span-7{grid-column:span 7/span 7}.row-span-1{grid-row:span 1/span 1}.row-start-1{grid-row-start:1}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-8{margin-bottom:2rem}.mr-10{margin-right:2.5rem}.mt-1{margin-top:.25rem}.mt-10{margin-top:2.5rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-full{height:100%}.max-h-screen{max-height:100vh}.min-h-full{min-height:100%}.min-h-screen{min-height:100vh}.w-10{width:2.5rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-full{width:100%}.min-w-2{min-width:.5rem}.min-w-48{min-width:12rem}.max-w-screen-max{max-width:98.75rem}.shrink-0{flex-shrink:0}.grow-\[2\]{flex-grow:2}.-translate-y-1\/2{--tw-translate-y:-50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.list-none{list-style-type:none}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-flow-dense{grid-auto-flow:dense}.auto-rows-min{grid-auto-rows:min-content}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-8{gap:2rem}.gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-y-2{row-gap:.5rem}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.rounded{border-radius:.375rem}.rounded-sm{border-radius:.25rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-t{border-top-width:1px}.border-gray-100{--tw-border-opacity:1;border-color:rgb(226 226 226/var(--tw-border-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.bg-gray-300{--tw-bg-opacity:1;background-color:rgb(153 153 153/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.bg-light{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-transparent{background-color:transparent}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-4{padding:1rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-10{padding-left:2.5rem;padding-right:2.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-px{padding-top:1px;padding-bottom:1px}.pb-24{padding-bottom:6rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pb-6{padding-bottom:1.5rem}.pb-8{padding-bottom:2rem}.pl-4{padding-left:1rem}.pr-10{padding-right:2.5rem}.pt-0\.5{padding-top:.125rem}.pt-2{padding-top:.5rem}.font-mono{font-family:Roboto,Menlo,Consolas,Ubuntu Mono,Roboto Mono,DejaVu Sans Mono,monospace;}.text-100{font-size:.875rem}.text-200{font-size:1rem}.text-50{font-size:.75rem}.text-600{font-size:1.5rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.capitalize{text-transform:capitalize}.leading-tight{line-height:1.25}.text-gray-300{--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(124 124 124/var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity:1;color:rgb(19 19 19/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(8 8 9/var(--tw-text-opacity))}.text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.text-light{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.outline-none{outline:2px solid transparent;outline-offset:2px}.text-stroke{-webkit-text-stroke:currentColor;-webkit-text-stroke-width:.6px}.no-scrollbar::-webkit-scrollbar{display:none}.no-scrollbar{-ms-overflow-style:none;scrollbar-width:none}.\*\:pl-0>*{padding-left:0}.before\:px-\[0\.18rem\]:before{content:var(--tw-content);padding-left:.18rem;padding-right:.18rem}.before\:text-gray-300:before{content:var(--tw-content);--tw-text-opacity:1;color:rgb(153 153 153/var(--tw-text-opacity))}.before\:content-\[\'\/\'\]:before{--tw-content:"/";content:var(--tw-content)}.before\:content-\[\'\:\'\]:before{--tw-content:":";content:var(--tw-content)}.before\:content-\[\'open\'\]:before{--tw-content:"open";content:var(--tw-content)}.after\:pointer-events-none:after{content:var(--tw-content);pointer-events:none}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:bottom-0:after{content:var(--tw-content);bottom:0}.after\:left-0:after{content:var(--tw-content);left:0}.after\:top-0:after{content:var(--tw-content);top:0}.after\:block:after{content:var(--tw-content);display:block}.after\:h-1:after{content:var(--tw-content);height:.25rem}.after\:h-full:after{content:var(--tw-content);height:100%}.after\:w-full:after{content:var(--tw-content);width:100%}.after\:rounded-t-sm:after{content:var(--tw-content);border-top-left-radius:.25rem;border-top-right-radius:.25rem}.after\:bg-gray-100:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.after\:bg-green-600:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.first\:border-t:first-child{border-top-width:1px}.hover\:border-gray-300:hover{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(226 226 226/var(--tw-bg-opacity))}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(240 240 240/var(--tw-bg-opacity))}.hover\:bg-green-600:hover{--tw-bg-opacity:1;background-color:rgb(34 108 87/var(--tw-bg-opacity))}.hover\:text-gray-600:hover{--tw-text-opacity:1;color:rgb(84 89 93/var(--tw-text-opacity))}.hover\:text-green-600:hover{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.hover\:text-light:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.hover\:underline:hover{text-decoration-line:underline}.focus\:border-gray-300:focus{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.focus\:border-l-gray-300:focus{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-gray-300{--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.group:hover .group-hover\:border-l-gray-300{--tw-border-opacity:1;border-left-color:rgb(153 153 153/var(--tw-border-opacity))}.group.is-active .group-\[\.is-active\]\:text-green-600{--tw-text-opacity:1;color:rgb(34 108 87/var(--tw-text-opacity))}.peer:checked~.peer-checked\:before\:content-\[\'close\'\]:before{--tw-content:"close";content:var(--tw-content)}.peer:focus-within~.peer-focus-within\:hidden{display:none}.has-\[ul\:empty\]\:hidden:has(ul:empty){display:none}.has-\[\:focus-within\]\:border-gray-300:has(:focus-within){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}.has-\[\:focus\]\:border-gray-300:has(:focus){--tw-border-opacity:1;border-color:rgb(153 153 153/var(--tw-border-opacity))}@media (min-width:30rem){.sm\:gap-6{gap:1.5rem}}@media (min-width:40rem){.md\:col-span-3{grid-column:span 3/span 3}.md\:mb-0{margin-bottom:0}.md\:h-4{height:1rem}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:items-center{align-items:center}.md\:gap-x-8{-moz-column-gap:2rem;column-gap:2rem}.md\:px-10{padding-left:2.5rem;padding-right:2.5rem}.md\:pb-0{padding-bottom:0}}@media (min-width:51.25rem){.lg\:order-2{order:2}.lg\:col-span-3{grid-column:span 3/span 3}.lg\:col-span-7{grid-column:span 7/span 7}.lg\:row-span-2{grid-row:span 2/span 2}.lg\:row-start-1{grid-row-start:1}.lg\:row-start-2{grid-row-start:2}.lg\:mb-4{margin-bottom:1rem}.lg\:mt-0{margin-top:0}.lg\:mt-10{margin-top:2.5rem}.lg\:block{display:block}.lg\:hidden{display:none}.lg\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.lg\:flex-row{flex-direction:row}.lg\:justify-start{justify-content:flex-start}.lg\:justify-between{justify-content:space-between}.lg\:gap-x-20{-moz-column-gap:5rem;column-gap:5rem}.lg\:border-none{border-style:none}.lg\:bg-transparent{background-color:transparent}.lg\:p-0{padding:0}.lg\:px-0{padding-left:0;padding-right:0}.lg\:px-2{padding-left:.5rem;padding-right:.5rem}.lg\:py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.lg\:pb-28{padding-bottom:7rem}.lg\:pt-2{padding-top:.5rem}.lg\:text-200{font-size:1rem}.lg\:font-semibold{font-weight:600}.lg\:hover\:bg-transparent:hover{background-color:transparent}}@media (min-width:63.75rem){.xl\:inline{display:inline}.xl\:hidden{display:none}.xl\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xl\:flex-row{flex-direction:row}.xl\:items-center{align-items:center}.xl\:gap-20{gap:5rem}.xl\:gap-6{gap:1.5rem}.xl\:pt-0{padding-top:0}}@media (min-width:85.375rem){.xxl\:inline-block{display:inline-block}.xxl\:h-4{height:1rem}.xxl\:w-4{width:1rem}.xxl\:gap-20{gap:5rem}.xxl\:gap-x-32{-moz-column-gap:8rem;column-gap:8rem}.xxl\:pr-1{padding-right:.25rem}} \ No newline at end of file From 870cbedf24d6cc4127f0274897cd28752a33c7e5 Mon Sep 17 00:00:00 2001 From: Guilhem Fanton <8671905+gfanton@users.noreply.github.com> Date: Thu, 16 Jan 2025 15:37:34 +0100 Subject: [PATCH 20/20] chore(gnoweb): cleanup iteration on gnoweb (#3379) depends on #3366 This PR cleans up, documents, and reorganizes the `gnoweb` package, which was recently revamped: * Refactored the code for better readability and structure, and added enhanced comments. * Enhanced existing test cases: * Added new test cases for `assets` in `app_test.go`. * Included a new test rule in the Makefile. * Created new tests for WebHandler in `handler_test.go`. * Improved file and directory handling methods in `handler.go`. --------- Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> Co-authored-by: Morgan --- gno.land/cmd/gnoweb/main.go | 14 +- gno.land/pkg/gnoweb/Makefile | 3 + gno.land/pkg/gnoweb/app.go | 80 ++++--- gno.land/pkg/gnoweb/app_test.go | 29 ++- gno.land/pkg/gnoweb/format.go | 69 ++++++ gno.land/pkg/gnoweb/formatter.go | 25 --- gno.land/pkg/gnoweb/handler.go | 304 ++++++++++++-------------- gno.land/pkg/gnoweb/handler_test.go | 112 ++++++++++ gno.land/pkg/gnoweb/markdown/toc.go | 4 +- gno.land/pkg/gnoweb/webclient.go | 136 +++--------- gno.land/pkg/gnoweb/webclient_html.go | 190 ++++++++++++++++ gno.land/pkg/gnoweb/webclient_mock.go | 91 ++++++++ 12 files changed, 710 insertions(+), 347 deletions(-) create mode 100644 gno.land/pkg/gnoweb/format.go delete mode 100644 gno.land/pkg/gnoweb/formatter.go create mode 100644 gno.land/pkg/gnoweb/handler_test.go create mode 100644 gno.land/pkg/gnoweb/webclient_html.go create mode 100644 gno.land/pkg/gnoweb/webclient_mock.go diff --git a/gno.land/cmd/gnoweb/main.go b/gno.land/cmd/gnoweb/main.go index 6500e44fcc4..8c0df00aa35 100644 --- a/gno.land/cmd/gnoweb/main.go +++ b/gno.land/cmd/gnoweb/main.go @@ -144,7 +144,6 @@ func setupWeb(cfg *webCfg, _ []string, io commands.IO) (func() error, error) { if cfg.verbose { level = zapcore.DebugLevel } - var zapLogger *zap.Logger if cfg.json { zapLogger = log.NewZapJSONLogger(io.Out(), level) @@ -155,23 +154,24 @@ func setupWeb(cfg *webCfg, _ []string, io commands.IO) (func() error, error) { logger := log.ZapLoggerToSlog(zapLogger) + // Setup app appcfg := gnoweb.NewDefaultAppConfig() appcfg.ChainID = cfg.chainid appcfg.NodeRemote = cfg.remote appcfg.RemoteHelp = cfg.remoteHelp + if appcfg.RemoteHelp == "" { + appcfg.RemoteHelp = appcfg.NodeRemote + } appcfg.Analytics = cfg.analytics appcfg.UnsafeHTML = cfg.html appcfg.FaucetURL = cfg.faucetURL appcfg.AssetsDir = cfg.assetsDir - if appcfg.RemoteHelp == "" { - appcfg.RemoteHelp = appcfg.NodeRemote - } - app, err := gnoweb.NewRouter(logger, appcfg) if err != nil { return nil, fmt.Errorf("unable to start gnoweb app: %w", err) } + // Resolve binding address bindaddr, err := net.ResolveTCPAddr("tcp", cfg.bind) if err != nil { return nil, fmt.Errorf("unable to resolve listener %q: %w", cfg.bind, err) @@ -179,6 +179,7 @@ func setupWeb(cfg *webCfg, _ []string, io commands.IO) (func() error, error) { logger.Info("Running", "listener", bindaddr.String()) + // Setup server server := &http.Server{ Handler: app, Addr: bindaddr.String(), @@ -187,10 +188,9 @@ func setupWeb(cfg *webCfg, _ []string, io commands.IO) (func() error, error) { return func() error { if err := server.ListenAndServe(); err != nil { - logger.Error("HTTP server stopped", " error:", err) + logger.Error("HTTP server stopped", "error", err) return commands.ExitCodeError(1) } - return nil }, nil } diff --git a/gno.land/pkg/gnoweb/Makefile b/gno.land/pkg/gnoweb/Makefile index 39c9d20ab10..8e8b6bf1a2c 100644 --- a/gno.land/pkg/gnoweb/Makefile +++ b/gno.land/pkg/gnoweb/Makefile @@ -39,6 +39,9 @@ cache_dir := .cache # Install dependencies all: generate +test: + go test -v ./... + # Generate process generate: css ts static diff --git a/gno.land/pkg/gnoweb/app.go b/gno.land/pkg/gnoweb/app.go index e99aa7ea056..516d3b92186 100644 --- a/gno.land/pkg/gnoweb/app.go +++ b/gno.land/pkg/gnoweb/app.go @@ -33,10 +33,12 @@ type AppConfig struct { ChainID string // AssetsPath is the base path to the gnoweb assets. AssetsPath string - // AssetDir, if set, will be used for assets instead of the embedded public directory + // AssetDir, if set, will be used for assets instead of the embedded public directory. AssetsDir string // FaucetURL, if specified, will be the URL to which `/faucet` redirects. FaucetURL string + // Domain is the domain used by the node. + Domain string } // NewDefaultAppConfig returns a new default [AppConfig]. The default sets @@ -44,17 +46,16 @@ type AppConfig struct { // to be served on /public/. func NewDefaultAppConfig() *AppConfig { const defaultRemote = "127.0.0.1:26657" - return &AppConfig{ - // same as Remote by default NodeRemote: defaultRemote, RemoteHelp: defaultRemote, ChainID: "dev", AssetsPath: "/public/", + Domain: "gno.land", } } -var chromaStyle = mustGetStyle("friendly") +var chromaDefaultStyle = mustGetStyle("friendly") func mustGetStyle(name string) *chroma.Style { s := styles.Get(name) @@ -64,15 +65,24 @@ func mustGetStyle(name string) *chroma.Style { return s } -// NewRouter initializes the gnoweb router, with the given logger and config. +// NewRouter initializes the gnoweb router with the specified logger and configuration. func NewRouter(logger *slog.Logger, cfg *AppConfig) (http.Handler, error) { + // Initialize RPC Client + client, err := client.NewHTTPClient(cfg.NodeRemote) + if err != nil { + return nil, fmt.Errorf("unable to create HTTP client: %w", err) + } + + // Configure Chroma highlighter chromaOptions := []chromahtml.Option{ chromahtml.WithLineNumbers(true), chromahtml.WithLinkableLineNumbers(true, "L"), chromahtml.WithClasses(true), chromahtml.ClassPrefix("chroma-"), } + chroma := chromahtml.New(chromaOptions...) + // Configure Goldmark markdown parser mdopts := []goldmark.Option{ goldmark.WithExtensions( markdown.NewHighlighting( @@ -84,36 +94,41 @@ func NewRouter(logger *slog.Logger, cfg *AppConfig) (http.Handler, error) { if cfg.UnsafeHTML { mdopts = append(mdopts, goldmark.WithRendererOptions(mdhtml.WithXHTML(), mdhtml.WithUnsafe())) } - md := goldmark.New(mdopts...) - client, err := client.NewHTTPClient(cfg.NodeRemote) - if err != nil { - return nil, fmt.Errorf("unable to create http client: %w", err) + // Configure WebClient + webcfg := HTMLWebClientConfig{ + Markdown: md, + Highlighter: NewChromaSourceHighlighter(chroma, chromaDefaultStyle), + Domain: cfg.Domain, + UnsafeHTML: cfg.UnsafeHTML, + RPCClient: client, } - webcli := NewWebClient(logger, client, md) - formatter := chromahtml.New(chromaOptions...) + webcli := NewHTMLClient(logger, &webcfg) chromaStylePath := path.Join(cfg.AssetsPath, "_chroma", "style.css") - var webConfig WebHandlerConfig - - webConfig.RenderClient = webcli - webConfig.Formatter = newFormatterWithStyle(formatter, chromaStyle) - - // Static meta - webConfig.Meta.AssetsPath = cfg.AssetsPath - webConfig.Meta.ChromaPath = chromaStylePath - webConfig.Meta.RemoteHelp = cfg.RemoteHelp - webConfig.Meta.ChainId = cfg.ChainID - webConfig.Meta.Analytics = cfg.Analytics + // Setup StaticMetadata + staticMeta := StaticMetadata{ + Domain: cfg.Domain, + AssetsPath: cfg.AssetsPath, + ChromaPath: chromaStylePath, + RemoteHelp: cfg.RemoteHelp, + ChainId: cfg.ChainID, + Analytics: cfg.Analytics, + } - // Setup main handler - webhandler := NewWebHandler(logger, webConfig) + // Configure WebHandler + webConfig := WebHandlerConfig{WebClient: webcli, Meta: staticMeta} + webhandler, err := NewWebHandler(logger, webConfig) + if err != nil { + return nil, fmt.Errorf("unable to create web handler: %w", err) + } + // Setup HTTP muxer mux := http.NewServeMux() - // Setup Webahndler along Alias Middleware + // Handle web handler with alias middleware mux.Handle("/", AliasAndRedirectMiddleware(webhandler, cfg.Analytics)) // Register faucet URL to `/faucet` if specified @@ -127,22 +142,21 @@ func NewRouter(logger *slog.Logger, cfg *AppConfig) (http.Handler, error) { })) } - // setup assets + // Handle Chroma CSS requests + // XXX: probably move this elsewhere mux.Handle(chromaStylePath, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Setup Formatter w.Header().Set("Content-Type", "text/css") - if err := formatter.WriteCSS(w, chromaStyle); err != nil { - logger.Error("unable to write css", "err", err) + if err := chroma.WriteCSS(w, chromaDefaultStyle); err != nil { + logger.Error("unable to write CSS", "err", err) http.NotFound(w, r) } })) - // Normalize assets path - assetsBase := "/" + strings.Trim(cfg.AssetsPath, "/") + "/" - // Handle assets path + // XXX: add caching + assetsBase := "/" + strings.Trim(cfg.AssetsPath, "/") + "/" if cfg.AssetsDir != "" { - logger.Debug("using assets dir instead of embed assets", "dir", cfg.AssetsDir) + logger.Debug("using assets dir instead of embedded assets", "dir", cfg.AssetsDir) mux.Handle(assetsBase, DevAssetHandler(assetsBase, cfg.AssetsDir)) } else { mux.Handle(assetsBase, AssetHandler()) diff --git a/gno.land/pkg/gnoweb/app_test.go b/gno.land/pkg/gnoweb/app_test.go index 4fac6e0b971..9f8f87b99b1 100644 --- a/gno.land/pkg/gnoweb/app_test.go +++ b/gno.land/pkg/gnoweb/app_test.go @@ -24,9 +24,9 @@ func TestRoutes(t *testing.T) { status int substring string }{ - {"/", ok, "Welcome"}, // assert / gives 200 (OK). assert / contains "Welcome". + {"/", ok, "Welcome"}, // Check if / returns 200 (OK) and contains "Welcome". {"/about", ok, "blockchain"}, - {"/r/gnoland/blog", ok, ""}, // whatever content + {"/r/gnoland/blog", ok, ""}, // Any content {"/r/gnoland/blog$help", ok, "AdminSetAdminAddr"}, {"/r/gnoland/blog/", ok, "admin.gno"}, {"/r/gnoland/blog/admin.gno", ok, ">func<"}, @@ -47,12 +47,18 @@ func TestRoutes(t *testing.T) { {"/game-of-realms", found, "/contribute"}, {"/gor", found, "/contribute"}, {"/blog", found, "/r/gnoland/blog"}, - {"/404/not/found/", notFound, ""}, + {"/r/not/found/", notFound, ""}, + {"/404/not/found", notFound, ""}, {"/아스키문자가아닌경로", notFound, ""}, {"/%ED%85%8C%EC%8A%A4%ED%8A%B8", notFound, ""}, {"/グノー", notFound, ""}, - {"/⚛️", notFound, ""}, + {"/\u269B\uFE0F", notFound, ""}, // Unicode {"/p/demo/flow/LICENSE", ok, "BSD 3-Clause"}, + // Test assets + {"/public/styles.css", ok, ""}, + {"/public/js/index.js", ok, ""}, + {"/public/_chroma/style.css", ok, ""}, + {"/public/imgs/gnoland.svg", ok, ""}, } rootdir := gnoenv.RootDir() @@ -66,8 +72,7 @@ func TestRoutes(t *testing.T) { logger := log.NewTestingLogger(t) - // set the `remoteAddr` of the client to the listening address of the - // node, which is randomly assigned. + // Initialize the router with the current node's remote address router, err := NewRouter(logger, cfg) require.NoError(t, err) @@ -85,24 +90,24 @@ func TestRoutes(t *testing.T) { func TestAnalytics(t *testing.T) { routes := []string{ - // special realms - "/", // home + // Special realms + "/", // Home "/about", "/start", - // redirects + // Redirects "/game-of-realms", "/getting-started", "/blog", "/boards", - // realm, source, help page + // Realm, source, help page "/r/gnoland/blog", "/r/gnoland/blog/admin.gno", "/r/demo/users:administrator", "/r/gnoland/blog$help", - // special pages + // Special pages "/404-not-found", } @@ -125,6 +130,7 @@ func TestAnalytics(t *testing.T) { request := httptest.NewRequest(http.MethodGet, route, nil) response := httptest.NewRecorder() + router.ServeHTTP(response, request) assert.Contains(t, response.Body.String(), "sa.gno.services") @@ -143,6 +149,7 @@ func TestAnalytics(t *testing.T) { request := httptest.NewRequest(http.MethodGet, route, nil) response := httptest.NewRecorder() + router.ServeHTTP(response, request) assert.NotContains(t, response.Body.String(), "sa.gno.services") diff --git a/gno.land/pkg/gnoweb/format.go b/gno.land/pkg/gnoweb/format.go new file mode 100644 index 00000000000..67911bfa985 --- /dev/null +++ b/gno.land/pkg/gnoweb/format.go @@ -0,0 +1,69 @@ +package gnoweb + +import ( + "fmt" + "io" + "path/filepath" + "strings" + + "github.com/alecthomas/chroma/v2" + "github.com/alecthomas/chroma/v2/formatters/html" + "github.com/alecthomas/chroma/v2/lexers" +) + +// FormatSource defines the interface for formatting source code. +type FormatSource interface { + Format(w io.Writer, fileName string, file []byte) error +} + +// ChromaSourceHighlighter implements the Highlighter interface using the Chroma library. +type ChromaSourceHighlighter struct { + *html.Formatter + style *chroma.Style +} + +// NewChromaSourceHighlighter constructs a new ChromaHighlighter with the given formatter and style. +func NewChromaSourceHighlighter(formatter *html.Formatter, style *chroma.Style) FormatSource { + return &ChromaSourceHighlighter{Formatter: formatter, style: style} +} + +// Format applies syntax highlighting to the source code using Chroma. +func (f *ChromaSourceHighlighter) Format(w io.Writer, fileName string, src []byte) error { + var lexer chroma.Lexer + + // Determine the lexer to be used based on the file extension. + switch strings.ToLower(filepath.Ext(fileName)) { + case ".gno": + lexer = lexers.Get("go") + case ".md": + lexer = lexers.Get("markdown") + case ".mod": + lexer = lexers.Get("gomod") + default: + lexer = lexers.Get("txt") // Unsupported file type, default to plain text. + } + + if lexer == nil { + return fmt.Errorf("unsupported lexer for file %q", fileName) + } + + iterator, err := lexer.Tokenise(nil, string(src)) + if err != nil { + return fmt.Errorf("unable to tokenise %q: %w", fileName, err) + } + + if err := f.Formatter.Format(w, f.style, iterator); err != nil { + return fmt.Errorf("unable to format source file %q: %w", fileName, err) + } + + return nil +} + +// noopFormat is a no-operation highlighter that writes the source code as-is. +type noopFormat struct{} + +// Format writes the source code to the writer without any formatting. +func (f *noopFormat) Format(w io.Writer, fileName string, src []byte) error { + _, err := w.Write(src) + return err +} diff --git a/gno.land/pkg/gnoweb/formatter.go b/gno.land/pkg/gnoweb/formatter.go deleted file mode 100644 index e172afe9e21..00000000000 --- a/gno.land/pkg/gnoweb/formatter.go +++ /dev/null @@ -1,25 +0,0 @@ -package gnoweb - -import ( - "io" - - "github.com/alecthomas/chroma/v2" - "github.com/alecthomas/chroma/v2/formatters/html" -) - -type Formatter interface { - Format(w io.Writer, iterator chroma.Iterator) error -} - -type formatterWithStyle struct { - *html.Formatter - style *chroma.Style -} - -func newFormatterWithStyle(formater *html.Formatter, style *chroma.Style) Formatter { - return &formatterWithStyle{Formatter: formater, style: style} -} - -func (f *formatterWithStyle) Format(w io.Writer, iterator chroma.Iterator) error { - return f.Formatter.Format(w, f.style, iterator) -} diff --git a/gno.land/pkg/gnoweb/handler.go b/gno.land/pkg/gnoweb/handler.go index 20f19e4405a..2dc51d64029 100644 --- a/gno.land/pkg/gnoweb/handler.go +++ b/gno.land/pkg/gnoweb/handler.go @@ -9,19 +9,16 @@ import ( "log/slog" "net/http" "path/filepath" - "slices" "strings" "time" - "github.com/alecthomas/chroma/v2" - "github.com/alecthomas/chroma/v2/lexers" "github.com/gnolang/gno/gno.land/pkg/gnoweb/components" - "github.com/gnolang/gno/gno.land/pkg/sdk/vm" // for error types + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" // For error types ) -const DefaultChainDomain = "gno.land" - +// StaticMetadata holds static configuration for a web handler. type StaticMetadata struct { + Domain string AssetsPath string ChromaPath string RemoteHelp string @@ -29,35 +26,43 @@ type StaticMetadata struct { Analytics bool } +// WebHandlerConfig configures a WebHandler. type WebHandlerConfig struct { - Meta StaticMetadata - RenderClient *WebClient - Formatter Formatter + Meta StaticMetadata + WebClient WebClient } -type WebHandler struct { - formatter Formatter +// validate checks if the WebHandlerConfig is valid. +func (cfg WebHandlerConfig) validate() error { + if cfg.WebClient == nil { + return errors.New("no `WebClient` configured") + } + return nil +} - logger *slog.Logger - static StaticMetadata - webcli *WebClient +// WebHandler processes HTTP requests. +type WebHandler struct { + Logger *slog.Logger + Static StaticMetadata + Client WebClient } -func NewWebHandler(logger *slog.Logger, cfg WebHandlerConfig) *WebHandler { - if cfg.RenderClient == nil { - logger.Error("no renderer has been defined") +// NewWebHandler creates a new WebHandler. +func NewWebHandler(logger *slog.Logger, cfg WebHandlerConfig) (*WebHandler, error) { + if err := cfg.validate(); err != nil { + return nil, fmt.Errorf("config validate error: %w", err) } return &WebHandler{ - formatter: cfg.Formatter, - webcli: cfg.RenderClient, - logger: logger, - static: cfg.Meta, - } + Client: cfg.WebClient, + Static: cfg.Meta, + Logger: logger, + }, nil } +// ServeHTTP handles HTTP requests. func (h *WebHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - h.logger.Debug("receiving request", "method", r.Method, "path", r.URL.Path) + h.Logger.Debug("receiving request", "method", r.Method, "path", r.URL.Path) if r.Method != http.MethodGet { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) @@ -67,47 +72,29 @@ func (h *WebHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { h.Get(w, r) } +// Get processes a GET HTTP request. func (h *WebHandler) Get(w http.ResponseWriter, r *http.Request) { var body bytes.Buffer start := time.Now() defer func() { - h.logger.Debug("request completed", + h.Logger.Debug("request completed", "url", r.URL.String(), "elapsed", time.Since(start).String()) }() - var indexData components.IndexData - indexData.HeadData.AssetsPath = h.static.AssetsPath - indexData.HeadData.ChromaPath = h.static.ChromaPath - indexData.FooterData.Analytics = h.static.Analytics - indexData.FooterData.AssetsPath = h.static.AssetsPath - - // Render the page body into the buffer - var status int - gnourl, err := ParseGnoURL(r.URL) - if err != nil { - h.logger.Warn("page not found", "path", r.URL.Path, "err", err) - status, err = http.StatusNotFound, components.RenderStatusComponent(&body, "page not found") - } else { - // TODO: real data (title & description) - indexData.HeadData.Title = "gno.land - " + gnourl.Path - - // Header - indexData.HeaderData.RealmPath = gnourl.Encode(EncodePath | EncodeArgs | EncodeQuery | EncodeNoEscape) - indexData.HeaderData.Breadcrumb = generateBreadcrumbPaths(gnourl) - indexData.HeaderData.WebQuery = gnourl.WebQuery - - // Render - switch { - case gnourl.IsRealm(), gnourl.IsPure(): - status, err = h.renderPackage(&body, gnourl) - default: - h.logger.Debug("invalid path: path is neither a pure package or a realm") - status, err = http.StatusNotFound, components.RenderStatusComponent(&body, "page not found") - } + indexData := components.IndexData{ + HeadData: components.HeadData{ + AssetsPath: h.Static.AssetsPath, + ChromaPath: h.Static.ChromaPath, + }, + FooterData: components.FooterData{ + Analytics: h.Static.Analytics, + AssetsPath: h.Static.AssetsPath, + }, } + status, err := h.renderPage(&body, r, &indexData) if err != nil { http.Error(w, "internal server error", http.StatusInternalServerError) return @@ -120,75 +107,99 @@ func (h *WebHandler) Get(w http.ResponseWriter, r *http.Request) { // Render the final page with the rendered body if err = components.RenderIndexComponent(w, indexData); err != nil { - h.logger.Error("failed to render index component", "err", err) + h.Logger.Error("failed to render index component", "err", err) } +} - return +// renderPage renders the page into the given buffer and prepares the index data. +func (h *WebHandler) renderPage(body *bytes.Buffer, r *http.Request, indexData *components.IndexData) (int, error) { + gnourl, err := ParseGnoURL(r.URL) + if err != nil { + h.Logger.Warn("unable to parse url path", "path", r.URL.Path, "err", err) + return http.StatusNotFound, components.RenderStatusComponent(body, "invalid path") + } + + breadcrumb := generateBreadcrumbPaths(gnourl) + indexData.HeadData.Title = h.Static.Domain + " - " + gnourl.Path + indexData.HeaderData = components.HeaderData{ + RealmPath: gnourl.Encode(EncodePath | EncodeArgs | EncodeQuery | EncodeNoEscape), + Breadcrumb: breadcrumb, + WebQuery: gnourl.WebQuery, + } + + switch { + case gnourl.IsRealm(), gnourl.IsPure(): + return h.GetPackagePage(body, gnourl) + default: + h.Logger.Debug("invalid path: path is neither a pure package or a realm") + return http.StatusBadRequest, components.RenderStatusComponent(body, "invalid path") + } } -func (h *WebHandler) renderPackage(w io.Writer, gnourl *GnoURL) (status int, err error) { - h.logger.Info("component render", "path", gnourl.Path, "args", gnourl.Args) +// GetPackagePage handles package pages. +func (h *WebHandler) GetPackagePage(w io.Writer, gnourl *GnoURL) (int, error) { + h.Logger.Info("component render", "path", gnourl.Path, "args", gnourl.Args) - // Display realm help page? + // Handle Help page if gnourl.WebQuery.Has("help") { - return h.renderRealmHelp(w, gnourl) + return h.GetHelpPage(w, gnourl) } - // Display package source page? - switch { - case gnourl.WebQuery.Has("source"): - return h.renderRealmSource(w, gnourl) - case gnourl.IsFile(): - // Fill webquery with file infos - return h.renderRealmSource(w, gnourl) - case gnourl.IsDir(), gnourl.IsPure(): - return h.renderRealmDirectory(w, gnourl) + // Handle Source page + if gnourl.WebQuery.Has("source") || gnourl.IsFile() { + return h.GetSourcePage(w, gnourl) + } + + // Handle Source page + if gnourl.IsDir() || gnourl.IsPure() { + return h.GetDirectoryPage(w, gnourl) } - // Render content into the content buffer + // Ultimately render realm content + return h.renderRealmContent(w, gnourl) +} + +// renderRealmContent renders the content of a realm. +func (h *WebHandler) renderRealmContent(w io.Writer, gnourl *GnoURL) (int, error) { var content bytes.Buffer - meta, err := h.webcli.Render(&content, gnourl.Path, gnourl.EncodeArgs()) + meta, err := h.Client.RenderRealm(&content, gnourl.Path, gnourl.EncodeArgs()) if err != nil { - if errors.Is(err, vm.InvalidPkgPathError{}) { - return http.StatusNotFound, components.RenderStatusComponent(w, "not found") - } - - h.logger.Error("unable to render markdown", "err", err) - return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") + h.Logger.Error("unable to render realm", "err", err, "path", gnourl.EncodeArgs()) + return renderClientErrorStatusPage(w, gnourl, err) } err = components.RenderRealmComponent(w, components.RealmData{ TocItems: &components.RealmTOCData{ - Items: meta.Items, + Items: meta.Toc.Items, }, - // NOTE: `content` should have already been escaped by + // NOTE: `RenderRealm` should ensure that HTML content is + // sanitized before rendering Content: template.HTML(content.String()), //nolint:gosec }) if err != nil { - h.logger.Error("unable to render template", "err", err) + h.Logger.Error("unable to render template", "err", err) return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") } - // Write the rendered content to the response writer return http.StatusOK, nil } -func (h *WebHandler) renderRealmHelp(w io.Writer, gnourl *GnoURL) (status int, err error) { - fsigs, err := h.webcli.Functions(gnourl.Path) +// GetHelpPage renders the help page. +func (h *WebHandler) GetHelpPage(w io.Writer, gnourl *GnoURL) (int, error) { + fsigs, err := h.Client.Functions(gnourl.Path) if err != nil { - h.logger.Error("unable to fetch path functions", "err", err) - return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") + h.Logger.Error("unable to fetch path functions", "err", err) + return renderClientErrorStatusPage(w, gnourl, err) } - var selArgs map[string]string - var selFn string - if selFn = gnourl.WebQuery.Get("func"); selFn != "" { + selArgs := make(map[string]string) + selFn := gnourl.WebQuery.Get("func") + if selFn != "" { for _, fn := range fsigs { if selFn != fn.FuncName { continue } - selArgs = make(map[string]string) for _, param := range fn.Params { selArgs[param.Name] = gnourl.WebQuery.Get(param.Name) } @@ -198,102 +209,87 @@ func (h *WebHandler) renderRealmHelp(w io.Writer, gnourl *GnoURL) (status int, e } } - // Catch last name of the path - // XXX: we should probably add a helper within the template realmName := filepath.Base(gnourl.Path) err = components.RenderHelpComponent(w, components.HelpData{ SelectedFunc: selFn, SelectedArgs: selArgs, RealmName: realmName, - ChainId: h.static.ChainId, + ChainId: h.Static.ChainId, // TODO: get chain domain and use that. - PkgPath: filepath.Join(DefaultChainDomain, gnourl.Path), - Remote: h.static.RemoteHelp, + PkgPath: filepath.Join(h.Static.Domain, gnourl.Path), + Remote: h.Static.RemoteHelp, Functions: fsigs, }) if err != nil { - h.logger.Error("unable to render helper", "err", err) + h.Logger.Error("unable to render helper", "err", err) return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") } return http.StatusOK, nil } -func (h *WebHandler) renderRealmSource(w io.Writer, gnourl *GnoURL) (status int, err error) { +// GetSource renders the source page. +func (h *WebHandler) GetSourcePage(w io.Writer, gnourl *GnoURL) (int, error) { pkgPath := gnourl.Path - - files, err := h.webcli.Sources(pkgPath) + files, err := h.Client.Sources(pkgPath) if err != nil { - h.logger.Error("unable to list sources file", "path", gnourl.Path, "err", err) - return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") + h.Logger.Error("unable to list sources file", "path", gnourl.Path, "err", err) + return renderClientErrorStatusPage(w, gnourl, err) } if len(files) == 0 { - h.logger.Debug("no files available", "path", gnourl.Path) + h.Logger.Debug("no files available", "path", gnourl.Path) return http.StatusOK, components.RenderStatusComponent(w, "no files available") } - file := gnourl.WebQuery.Get("file") // webquery override file - if file == "" { - file = gnourl.File - } - var fileName string - if file == "" { - fileName = files[0] // Default to the first file if none specified - } else if slices.Contains(files, file) { - fileName = file // Use specified file if it exists - } else { - h.logger.Error("unable to render source", "file", file, "err", "file does not exist") - return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") + if gnourl.IsFile() { // check path file from path first + fileName = gnourl.File + } else if file := gnourl.WebQuery.Get("file"); file != "" { + fileName = file } - source, err := h.webcli.SourceFile(pkgPath, fileName) - if err != nil { - h.logger.Error("unable to get source file", "file", fileName, "err", err) - return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") + if fileName == "" { + fileName = files[0] // fallback on the first file if } - // XXX: we should either do this on the front or in the markdown parsing side - fileLines := strings.Count(string(source), "\n") - fileSizeKb := float64(len(source)) / 1024.0 - fileSizeStr := fmt.Sprintf("%.2f Kb", fileSizeKb) - - // Highlight code source - hsource, err := h.highlightSource(fileName, source) + var source bytes.Buffer + meta, err := h.Client.SourceFile(&source, pkgPath, fileName) if err != nil { - h.logger.Error("unable to highlight source file", "file", fileName, "err", err) - return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") + h.Logger.Error("unable to get source file", "file", fileName, "err", err) + return renderClientErrorStatusPage(w, gnourl, err) } + fileSizeStr := fmt.Sprintf("%.2f Kb", meta.SizeKb) err = components.RenderSourceComponent(w, components.SourceData{ PkgPath: gnourl.Path, Files: files, FileName: fileName, FileCounter: len(files), - FileLines: fileLines, + FileLines: meta.Lines, FileSize: fileSizeStr, - FileSource: template.HTML(hsource), //nolint:gosec + FileSource: template.HTML(source.String()), //nolint:gosec }) if err != nil { - h.logger.Error("unable to render helper", "err", err) + h.Logger.Error("unable to render helper", "err", err) return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") } return http.StatusOK, nil } -func (h *WebHandler) renderRealmDirectory(w io.Writer, gnourl *GnoURL) (status int, err error) { - pkgPath := gnourl.Path +// GetDirectoryPage renders the directory page. +func (h *WebHandler) GetDirectoryPage(w io.Writer, gnourl *GnoURL) (int, error) { + pkgPath := strings.TrimSuffix(gnourl.Path, "/") - files, err := h.webcli.Sources(pkgPath) + files, err := h.Client.Sources(pkgPath) if err != nil { - h.logger.Error("unable to list sources file", "path", gnourl.Path, "err", err) - return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") + h.Logger.Error("unable to list sources file", "path", gnourl.Path, "err", err) + return renderClientErrorStatusPage(w, gnourl, err) } if len(files) == 0 { - h.logger.Debug("no files available", "path", gnourl.Path) + h.Logger.Debug("no files available", "path", gnourl.Path) return http.StatusOK, components.RenderStatusComponent(w, "no files available") } @@ -303,40 +299,28 @@ func (h *WebHandler) renderRealmDirectory(w io.Writer, gnourl *GnoURL) (status i FileCounter: len(files), }) if err != nil { - h.logger.Error("unable to render directory", "err", err) - return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") + h.Logger.Error("unable to render directory", "err", err) + return http.StatusInternalServerError, components.RenderStatusComponent(w, "not found") } return http.StatusOK, nil } -func (h *WebHandler) highlightSource(fileName string, src []byte) ([]byte, error) { - var lexer chroma.Lexer - - switch strings.ToLower(filepath.Ext(fileName)) { - case ".gno": - lexer = lexers.Get("go") - case ".md": - lexer = lexers.Get("markdown") - default: - lexer = lexers.Get("txt") // file kind not supported, fallback on `.txt` +func renderClientErrorStatusPage(w io.Writer, _ *GnoURL, err error) (int, error) { + if err == nil { + return http.StatusOK, nil } - if lexer == nil { - return nil, fmt.Errorf("unsupported lexer for file %q", fileName) - } - - iterator, err := lexer.Tokenise(nil, string(src)) - if err != nil { - h.logger.Error("unable to ", "fileName", fileName, "err", err) - } - - var buff bytes.Buffer - if err := h.formatter.Format(&buff, iterator); err != nil { - return nil, fmt.Errorf("unable to format source file %q: %w", fileName, err) + switch { + case errors.Is(err, ErrClientPathNotFound): + return http.StatusNotFound, components.RenderStatusComponent(w, err.Error()) + case errors.Is(err, ErrClientBadRequest): + return http.StatusInternalServerError, components.RenderStatusComponent(w, "bad request") + case errors.Is(err, ErrClientResponse): + fallthrough // XXX: for now fallback as internal error + default: + return http.StatusInternalServerError, components.RenderStatusComponent(w, "internal error") } - - return buff.Bytes(), nil } func generateBreadcrumbPaths(url *GnoURL) components.BreadcrumbData { diff --git a/gno.land/pkg/gnoweb/handler_test.go b/gno.land/pkg/gnoweb/handler_test.go new file mode 100644 index 00000000000..624e3390a97 --- /dev/null +++ b/gno.land/pkg/gnoweb/handler_test.go @@ -0,0 +1,112 @@ +package gnoweb_test + +import ( + "log/slog" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/gnolang/gno/gno.land/pkg/gnoweb" + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type testingLogger struct { + *testing.T +} + +func (t *testingLogger) Write(b []byte) (n int, err error) { + t.T.Log(strings.TrimSpace(string(b))) + return len(b), nil +} + +// TestWebHandler_Get tests the Get method of WebHandler using table-driven tests. +func TestWebHandler_Get(t *testing.T) { + // Set up a mock package with some files and functions + mockPackage := &gnoweb.MockPackage{ + Domain: "example.com", + Path: "/r/mock/path", + Files: map[string]string{ + "render.gno": `package main; func Render(path string) { return "one more time" }`, + "gno.mod": `module example.com/r/mock/path`, + "LicEnse": `my super license`, + }, + Functions: []vm.FunctionSignature{ + {FuncName: "SuperRenderFunction", Params: []vm.NamedType{ + {Name: "my_super_arg", Type: "string"}, + }}, + }, + } + + // Create a mock web client with the mock package + webclient := gnoweb.NewMockWebClient(mockPackage) + + // Create a WebHandlerConfig with the mock web client and static metadata + config := gnoweb.WebHandlerConfig{ + WebClient: webclient, + } + + // Define test cases + cases := []struct { + Path string + Status int + Contain string // optional + Contains []string // optional + }{ + // Found + {Path: "/r/mock/path", Status: http.StatusOK, Contain: "[example.com]/r/mock/path"}, + + // Source page + {Path: "/r/mock/path/", Status: http.StatusOK, Contain: "Directory"}, + {Path: "/r/mock/path/render.gno", Status: http.StatusOK, Contain: "one more time"}, + {Path: "/r/mock/path/LicEnse", Status: http.StatusOK, Contain: "my super license"}, + {Path: "/r/mock/path$source&file=render.gno", Status: http.StatusOK, Contain: "one more time"}, + {Path: "/r/mock/path$source", Status: http.StatusOK, Contain: "module"}, // `gno.mod` by default + {Path: "/r/mock/path/license", Status: http.StatusNotFound}, + + // Help page + {Path: "/r/mock/path$help", Status: http.StatusOK, Contains: []string{ + "my_super_arg", + "SuperRenderFunction", + }}, + + // Package not found + {Path: "/r/invalid/path", Status: http.StatusNotFound, Contain: "not found"}, + + // Invalid path + {Path: "/r", Status: http.StatusBadRequest, Contain: "invalid path"}, + {Path: "/r/~!1337", Status: http.StatusNotFound, Contain: "invalid path"}, + } + + for _, tc := range cases { + t.Run(strings.TrimPrefix(tc.Path, "/"), func(t *testing.T) { + t.Logf("input: %+v", tc) + + // Initialize testing logger + logger := slog.New(slog.NewTextHandler(&testingLogger{t}, &slog.HandlerOptions{})) + + // Create a new WebHandler + handler, err := gnoweb.NewWebHandler(logger, config) + require.NoError(t, err) + + // Create a new HTTP request for each test case + req, err := http.NewRequest(http.MethodGet, tc.Path, nil) + require.NoError(t, err) + + // Create a ResponseRecorder to capture the response + rr := httptest.NewRecorder() + + // Invoke serve method + handler.ServeHTTP(rr, req) + + // Assert result + assert.Equal(t, tc.Status, rr.Code) + assert.Containsf(t, rr.Body.String(), tc.Contain, "rendered body should contain: %q", tc.Contain) + for _, contain := range tc.Contains { + assert.Containsf(t, rr.Body.String(), contain, "rendered body should contain: %q", contain) + } + }) + } +} diff --git a/gno.land/pkg/gnoweb/markdown/toc.go b/gno.land/pkg/gnoweb/markdown/toc.go index 59d4941fabf..ceafbd7cc96 100644 --- a/gno.land/pkg/gnoweb/markdown/toc.go +++ b/gno.land/pkg/gnoweb/markdown/toc.go @@ -45,7 +45,7 @@ type TocOptions struct { MinDepth, MaxDepth int } -func TocInspect(n ast.Node, src []byte, opts TocOptions) (*Toc, error) { +func TocInspect(n ast.Node, src []byte, opts TocOptions) (Toc, error) { // Appends an empty subitem to the given node // and returns a reference to it. appendChild := func(n *TocItem) *TocItem { @@ -114,7 +114,7 @@ func TocInspect(n ast.Node, src []byte, opts TocOptions) (*Toc, error) { root.Items = compactItems(root.Items) - return &Toc{Items: root.Items}, err + return Toc{Items: root.Items}, err } // compactItems removes items with no titles diff --git a/gno.land/pkg/gnoweb/webclient.go b/gno.land/pkg/gnoweb/webclient.go index a1005baa0a5..de44303f352 100644 --- a/gno.land/pkg/gnoweb/webclient.go +++ b/gno.land/pkg/gnoweb/webclient.go @@ -2,126 +2,44 @@ package gnoweb import ( "errors" - "fmt" "io" - "log/slog" - "path/filepath" - "strings" md "github.com/gnolang/gno/gno.land/pkg/gnoweb/markdown" - "github.com/gnolang/gno/gno.land/pkg/sdk/vm" // for error types - "github.com/gnolang/gno/tm2/pkg/amino" - "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" - "github.com/yuin/goldmark" - "github.com/yuin/goldmark/parser" - "github.com/yuin/goldmark/text" + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" ) -type WebClient struct { - logger *slog.Logger - client *client.RPCClient - md goldmark.Markdown -} - -func NewWebClient(log *slog.Logger, cl *client.RPCClient, m goldmark.Markdown) *WebClient { - m.Parser().AddOptions(parser.WithAutoHeadingID()) - return &WebClient{ - logger: log, - client: cl, - md: m, - } -} - -func (s *WebClient) Functions(pkgPath string) ([]vm.FunctionSignature, error) { - const qpath = "vm/qfuncs" - - args := fmt.Sprintf("gno.land/%s", strings.Trim(pkgPath, "/")) - res, err := s.query(qpath, []byte(args)) - if err != nil { - return nil, fmt.Errorf("unable query funcs list: %w", err) - } - - var fsigs vm.FunctionSignatures - if err := amino.UnmarshalJSON(res, &fsigs); err != nil { - s.logger.Warn("unable to unmarshal fsigs, client is probably outdated ?") - return nil, fmt.Errorf("unable to unamarshal fsigs: %w", err) - } - - return fsigs, nil -} - -func (s *WebClient) SourceFile(path, fileName string) ([]byte, error) { - const qpath = "vm/qfile" - - fileName = strings.TrimSpace(fileName) // sanitize filename - if fileName == "" { - return nil, errors.New("empty filename given") // XXX -> ErrXXX - } - - // XXX: move this into gnoclient ? - path = fmt.Sprintf("gno.land/%s", strings.Trim(path, "/")) - path = filepath.Join(path, fileName) - return s.query(qpath, []byte(path)) -} - -func (s *WebClient) Sources(path string) ([]string, error) { - const qpath = "vm/qfile" - - // XXX: move this into gnoclient - path = fmt.Sprintf("gno.land/%s", strings.Trim(path, "/")) - res, err := s.query(qpath, []byte(path)) - if err != nil { - return nil, err - } +var ( + ErrClientPathNotFound = errors.New("package not found") + ErrClientBadRequest = errors.New("bad request") + ErrClientResponse = errors.New("node response error") +) - files := strings.Split(string(res), "\n") - return files, nil +type FileMeta struct { + Lines int + SizeKb float64 } -type Metadata struct { - *md.Toc +type RealmMeta struct { + Toc md.Toc } -func (s *WebClient) Render(w io.Writer, pkgPath string, args string) (*Metadata, error) { - const qpath = "vm/qrender" - - data := []byte(gnoPath(pkgPath, args)) - rawres, err := s.query(qpath, data) - if err != nil { - return nil, err - } - - doc := s.md.Parser().Parse(text.NewReader(rawres)) - if err := s.md.Renderer().Render(w, rawres, doc); err != nil { - return nil, fmt.Errorf("unable render real %q: %w", data, err) - } +// WebClient is an interface for interacting with package and node ressources. +type WebClient interface { + // RenderRealm renders the content of a realm from a given path and + // arguments into the giver `writer`. The method should ensures the rendered + // content is safely handled and formatted. + RenderRealm(w io.Writer, path string, args string) (*RealmMeta, error) - var meta Metadata - meta.Toc, err = md.TocInspect(doc, rawres, md.TocOptions{MaxDepth: 6, MinDepth: 2}) - if err != nil { - s.logger.Warn("unable to inspect for toc elements", "err", err) - } + // SourceFile fetches and writes the source file from a given + // package path and file name. The method should ensures the source + // file's content is safely handled and formatted. + SourceFile(w io.Writer, pkgPath, fileName string) (*FileMeta, error) - return &meta, nil -} - -func (s *WebClient) query(qpath string, data []byte) ([]byte, error) { - s.logger.Info("query", "qpath", qpath, "data", string(data)) - - qres, err := s.client.ABCIQuery(qpath, data) - if err != nil { - s.logger.Error("request error", "path", qpath, "data", string(data), "error", err) - return nil, fmt.Errorf("unable to query path %q: %w", qpath, err) - } - if qres.Response.Error != nil { - s.logger.Error("response error", "path", qpath, "log", qres.Response.Log) - return nil, qres.Response.Error - } - - return qres.Response.Data, nil -} + // Functions retrieves a list of function signatures from a + // specified package path. + Functions(path string) ([]vm.FunctionSignature, error) -func gnoPath(pkgPath, args string) string { - pkgPath = strings.Trim(pkgPath, "/") - return fmt.Sprintf("gno.land/%s:%s", pkgPath, args) + // Sources lists all source files available in a specified + // package path. + Sources(path string) ([]string, error) } diff --git a/gno.land/pkg/gnoweb/webclient_html.go b/gno.land/pkg/gnoweb/webclient_html.go new file mode 100644 index 00000000000..ffe2238df98 --- /dev/null +++ b/gno.land/pkg/gnoweb/webclient_html.go @@ -0,0 +1,190 @@ +package gnoweb + +import ( + "errors" + "fmt" + "io" + "log/slog" + "path/filepath" + "strings" + + md "github.com/gnolang/gno/gno.land/pkg/gnoweb/markdown" + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" // for error types + "github.com/gnolang/gno/tm2/pkg/amino" + "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" + "github.com/yuin/goldmark" + "github.com/yuin/goldmark/parser" + "github.com/yuin/goldmark/text" +) + +type HTMLWebClientConfig struct { + Domain string + UnsafeHTML bool + RPCClient *client.RPCClient + Highlighter FormatSource + Markdown goldmark.Markdown +} + +// NewDefaultHTMLWebClientConfig initializes a WebClientConfig with default settings. +// It sets up goldmark Markdown parsing options and default domain and highlighter. +func NewDefaultHTMLWebClientConfig(client *client.RPCClient) *HTMLWebClientConfig { + mdopts := []goldmark.Option{goldmark.WithParserOptions(parser.WithAutoHeadingID())} + return &HTMLWebClientConfig{ + Domain: "gno.land", + Highlighter: &noopFormat{}, + Markdown: goldmark.New(mdopts...), + RPCClient: client, + } +} + +type HTMLWebClient struct { + domain string + logger *slog.Logger + client *client.RPCClient + md goldmark.Markdown + highlighter FormatSource +} + +// NewHTMLClient creates a new instance of WebClient. +// It requires a configured logger and WebClientConfig. +func NewHTMLClient(log *slog.Logger, cfg *HTMLWebClientConfig) *HTMLWebClient { + return &HTMLWebClient{ + logger: log, + domain: cfg.Domain, + client: cfg.RPCClient, + md: cfg.Markdown, + highlighter: cfg.Highlighter, + } +} + +// Functions retrieves a list of function signatures from a +// specified package path. +func (s *HTMLWebClient) Functions(pkgPath string) ([]vm.FunctionSignature, error) { + const qpath = "vm/qfuncs" + + args := fmt.Sprintf("%s/%s", s.domain, strings.Trim(pkgPath, "/")) + res, err := s.query(qpath, []byte(args)) + if err != nil { + return nil, fmt.Errorf("unable to query func list: %w", err) + } + + var fsigs vm.FunctionSignatures + if err := amino.UnmarshalJSON(res, &fsigs); err != nil { + s.logger.Warn("unable to unmarshal function signatures, client is probably outdated") + return nil, fmt.Errorf("unable to unmarshal function signatures: %w", err) + } + + return fsigs, nil +} + +// SourceFile fetches and writes the source file from a given +// package path and file name to the provided writer. It uses +// Chroma for syntax highlighting source. +func (s *HTMLWebClient) SourceFile(w io.Writer, path, fileName string) (*FileMeta, error) { + const qpath = "vm/qfile" + + fileName = strings.TrimSpace(fileName) + if fileName == "" { + return nil, errors.New("empty filename given") // XXX: Consider creating a specific error variable + } + + // XXX: Consider moving this into gnoclient + fullPath := filepath.Join(s.domain, strings.Trim(path, "/"), fileName) + + source, err := s.query(qpath, []byte(fullPath)) + if err != nil { + // XXX: this is a bit ugly, we should make the keeper return an + // assertable error. + if strings.Contains(err.Error(), "not available") { + return nil, ErrClientPathNotFound + } + + return nil, err + } + + fileMeta := FileMeta{ + Lines: strings.Count(string(source), "\n"), + SizeKb: float64(len(source)) / 1024.0, + } + + // Use Chroma for syntax highlighting + if err := s.highlighter.Format(w, fileName, source); err != nil { + return nil, err + } + + return &fileMeta, nil +} + +// Sources lists all source files available in a specified +// package path by querying the RPC client. +func (s *HTMLWebClient) Sources(path string) ([]string, error) { + const qpath = "vm/qfile" + + // XXX: Consider moving this into gnoclient + pkgPath := strings.Trim(path, "/") + fullPath := fmt.Sprintf("%s/%s", s.domain, pkgPath) + res, err := s.query(qpath, []byte(fullPath)) + if err != nil { + // XXX: this is a bit ugly, we should make the keeper return an + // assertable error. + if strings.Contains(err.Error(), "not available") { + return nil, ErrClientPathNotFound + } + + return nil, err + } + + files := strings.Split(strings.TrimSpace(string(res)), "\n") + return files, nil +} + +// RenderRealm renders the content of a realm from a given path +// and arguments into the provided writer. It uses Goldmark for +// Markdown processing to generate HTML content. +func (s *HTMLWebClient) RenderRealm(w io.Writer, pkgPath string, args string) (*RealmMeta, error) { + const qpath = "vm/qrender" + + pkgPath = strings.Trim(pkgPath, "/") + data := fmt.Sprintf("%s/%s:%s", s.domain, pkgPath, args) + rawres, err := s.query(qpath, []byte(data)) + if err != nil { + return nil, err + } + + // Use Goldmark for Markdown parsing + doc := s.md.Parser().Parse(text.NewReader(rawres)) + if err := s.md.Renderer().Render(w, rawres, doc); err != nil { + return nil, fmt.Errorf("unable to render realm %q: %w", data, err) + } + + var meta RealmMeta + meta.Toc, err = md.TocInspect(doc, rawres, md.TocOptions{MaxDepth: 6, MinDepth: 2}) + if err != nil { + s.logger.Warn("unable to inspect for TOC elements", "error", err) + } + + return &meta, nil +} + +// query sends a query to the RPC client and returns the response +// data. +func (s *HTMLWebClient) query(qpath string, data []byte) ([]byte, error) { + s.logger.Info("query", "path", qpath, "data", string(data)) + + qres, err := s.client.ABCIQuery(qpath, data) + if err != nil { + s.logger.Debug("request error", "path", qpath, "data", string(data), "error", err) + return nil, fmt.Errorf("%w: %s", ErrClientBadRequest, err.Error()) + } + + if err = qres.Response.Error; err != nil { + if errors.Is(err, vm.InvalidPkgPathError{}) { + return nil, ErrClientPathNotFound + } + + s.logger.Error("response error", "path", qpath, "log", qres.Response.Log) + return nil, fmt.Errorf("%w: %s", ErrClientResponse, err.Error()) + } + + return qres.Response.Data, nil +} diff --git a/gno.land/pkg/gnoweb/webclient_mock.go b/gno.land/pkg/gnoweb/webclient_mock.go new file mode 100644 index 00000000000..451f5e237c3 --- /dev/null +++ b/gno.land/pkg/gnoweb/webclient_mock.go @@ -0,0 +1,91 @@ +package gnoweb + +import ( + "bytes" + "fmt" + "io" + "sort" + + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" +) + +// MockPackage represents a mock package with files and function signatures. +type MockPackage struct { + Path string + Domain string + Files map[string]string // filename -> body + Functions []vm.FunctionSignature +} + +// MockWebClient is a mock implementation of the Client interface. +type MockWebClient struct { + Packages map[string]*MockPackage // path -> package +} + +func NewMockWebClient(pkgs ...*MockPackage) *MockWebClient { + mpkgs := make(map[string]*MockPackage) + for _, pkg := range pkgs { + mpkgs[pkg.Path] = pkg + } + + return &MockWebClient{Packages: mpkgs} +} + +// Render simulates rendering a package by writing its content to the writer. +func (m *MockWebClient) RenderRealm(w io.Writer, path string, args string) (*RealmMeta, error) { + pkg, exists := m.Packages[path] + if !exists { + return nil, ErrClientPathNotFound + } + + fmt.Fprintf(w, "[%s]%s:", pkg.Domain, pkg.Path) + + // Return a dummy RealmMeta for simplicity + return &RealmMeta{}, nil +} + +// SourceFile simulates retrieving a source file's metadata. +func (m *MockWebClient) SourceFile(w io.Writer, pkgPath, fileName string) (*FileMeta, error) { + pkg, exists := m.Packages[pkgPath] + if !exists { + return nil, ErrClientPathNotFound + } + + if body, ok := pkg.Files[fileName]; ok { + w.Write([]byte(body)) + return &FileMeta{ + Lines: len(bytes.Split([]byte(body), []byte("\n"))), + SizeKb: float64(len(body)) / 1024.0, + }, nil + } + + return nil, ErrClientPathNotFound +} + +// Functions simulates retrieving function signatures from a package. +func (m *MockWebClient) Functions(path string) ([]vm.FunctionSignature, error) { + pkg, exists := m.Packages[path] + if !exists { + return nil, ErrClientPathNotFound + } + + return pkg.Functions, nil +} + +// Sources simulates listing all source files in a package. +func (m *MockWebClient) Sources(path string) ([]string, error) { + pkg, exists := m.Packages[path] + if !exists { + return nil, ErrClientPathNotFound + } + + fileNames := make([]string, 0, len(pkg.Files)) + for file := range pkg.Files { + fileNames = append(fileNames, file) + } + + // Sort for consistency + sort.Strings(fileNames) + + return fileNames, nil +}