zack scholl (@schollz)
october 12th, 2023
layout: false .left-column[
] .right-column[
Gno is an interpreted version of the programming language Go.
Gno was created by Cosmos co-founder Jae Kwon.
Gno is optimized for blockchain - it has deterministic execution for executing on distributed systems.
practically speaking, Gno is Go without crypto/rand
, web calls, and imports from non-deterministic libraries. Gno code is transpiled into Go code which then leverages the Go compiler system.
If you can write Go code, you can write Gno code.
If you write Gno code, you can immediately write "smart contracts".
.left-column[
] .right-column[
they are executed according to what is defined in the code, and their code cannot be changed.
smart contracts can be used to automate transactions, but are not limited to DeFi. they can be used to create incentivized social networks and rework how we interact with the web (i.e. "web3").
smart contracts written in Gno run within the Gno.land ecosystem.
]
.left-column[
] .right-column[
Gno.land is a platform to write smart contracts in Gno.
It is the first of a series of Gno Layer 1 chains.
It is built on Tendermint2, Cosmos/IBC, secured by Proof of Contribution.
It prioritizes simplicity, security, scalability, and transparency.
For example: https://test3.gno.land/r/demo/boards:testboard
Currently no main net, but we will get started by running the entire system locally.
]
.left-column[
] .right-column[
bytebeat is a minimal programming language for synthesized music.
was discovered by viznut in 2011 as a way to type a very short computer programs that generate chiptune music, for example this is a bytebeat program:
main(t){
for(;;t++) putchar(((t<<1)^((t<<1)+(t>>7)&t>>12)));
}
which you can take the raw output of and convert to audio:
gcc -o crowd crowd.c
./crowd | head -c 4M > crowd.raw
sox -r 8000 -c 1 -t u8 crowd.raw crowd.wav
.left-column[
] .right-column[
if you know Go, you know Gno.
I will help you get started with the tooling, the ecosystem, and some first steps into what can be done with smart contracts.
.left-column[
] .right-column[
what you need before we begin:
- linux system (mac os or gitpod may also be okay)
- visual studio code ide (best supported currently).
- golang v1.21.2+
open ide and install gnopls
and gofumpt
. then search for and install the Gno
VScode extension.
> go install -v github.com/harry-hov/gnopls@latest
> go install -v mvdan.cc/gofumpt@latest
.left-column[
] .right-column[
today we will use a forked version of Gno that removes limits for allocation and CPU usage and has some ready code for today's tutorial:
> git clone https://github.com/schollz/gno bytebeat-workshop
> cd bytebeat-workshop
> git checkout bytebeat-workshop
open up the gno
folder in the visual studio code ide. lets build everything first:
> make build
this will install the gno
toolchain, build the gno.land
that runs the gno.land
node (locally), and build the gnoweb
server that runs the frontend interface to gno.land.
.left-column[
] .right-column[
keys are central to blockchains that to track tokens. lets generate one.
> gnokey generate
brush laugh ...
copy the bip39 mnemonic (brush laugh...
). Now we will actually add the key:
> gnokey add --recover mykey
enter a passphrase twice and then the bip39 mnemonic you copied.
now you should see your key when listing them:
> gnokey list
0. mykey (local) - addr: youraddress ...
.left-column[
] .right-column[
since we are spinning up our own testnet, we can add tokens directly to our key from the genesis block.
open gno.land/genesis/genesis_balances.txt
and add a new line with your address:
youraddress=10000000000ugnot
now when we run gno.land the address associated with mykey
will be allocated with 10,000,000,000 gnots.
.left-column[
] .right-column[
writing a smart contract is Gno is as easy as writing a package in Go.
however, Gno distinguishes between a package and a realm.
A package is Gno code that does not have state. Usually it is code that may be used by many realms. However you can also import realms. This can have any functions or structures exported to be used within realms.
A realm represents the actual smart contract - it is Gno code with state, storage, and can use tokens. Realms have a Render(path string) string
function that can be called from gno.land
. Globals persist.
lets write a smart contract.
.left-column[
] .right-column[
lets write some packages and realms in Gno that makes it easy to generate a smart contract to generate bytebeat audio on gno.land.
in the future this could be extended to a small web3 social network of music + code sharing where contributions are incentivized and audio streams are rewarded back to their creators.
.left-column[
] .right-column[
streaming audio comes in many formats, but one of the most common for uncompressed audio is the WAVE File format (.wav
) which is a subset of the RIFF file format. the canonical wave file format is well-defined:
.left-column[
] .right-column[
lets write our first Gno package to make it easier to write RIFF files. it will be a simply io.Writer
wrapper to help writing chunks needed for a RIFF header:
(w *Writer) WriteChunk(chunkID []byte, chunkSize uint32) (n int, err error)
we will work in the examples/gno.land/p/demo
folder (/p/
designates package, and /r/
will designate a realm). lets create the audio
folder and a riff
folder in there to hold our gno file.
lets now create the examples/gno.land/p/demo/riff/riff.gno
file.
.left-column[
] .right-column[
lets create and write code for RIFF:
./examples/gno.land/p/demo/audio/riff/riff.gno
.left-column[
] .right-column[
testing is done the same way as Go. you create a test package yourfile_test.gno
and you can then create automated tests.
there is already one setup for the riff
package:
./examples/gno.land/p/demo/audio/riff/riff_test.gno
you can run a test using the gno
tool:
> gno test --verbose examples/gno.land/p/demo/audio/riff
== RUN TestRiff
PASS: TestRiff (0.00s)
ok ./examples/gno.land/p/demo/audio/riff 1.13s
.left-column[
] .right-column[
now that we have examples/gno.land/p/demo/riff/riff.gno
we can create another package for handling wav files. this file is examples/gno.land/p/demo/wav/wav.gno
and is based on an open-source Go package for handling wav files. this file will ease the creation of wave files and adding samples. we will create a writer:
func NewWriter(w io.Writer,
numSamples uint32,
numChannels uint16,
sampleRate uint32,
bitsPerSample uint16) (writer *Writer, err error) ...
and a function for writing samples:
func (w *Writer) WriteSamples(samples []Sample) (err error)
.left-column[
] .right-column[
lets look at the code for the wav package
./examples/gno.land/p/demo/audio/wav/wav.gno
.left-column[
] .right-column[
now we can utilize the packages we've created (riff
and wav
) and create a bytebeat package that utilizes them.
the bytebeat package will export a single function that can take an argument for processing a bytebeat function:
func ByteBeat(seconds uint32,
sampleRate uint32,
bytebeat_func func(t int) int) (data string)
since this package is designed to be used with the bytebeat realm, we will return a string
since gno.land communicates through strings. in this case the string will be the base64-encoded WAVE file format audio.
.left-column[
] .right-column[
lets look at the code for the bytebeat package:
./examples/gno.land/p/demo/audio/bytebeat/bytebeat.gno
.left-column[
] .right-column[
we will write a test for this package that enables us to generate + playback the audio. find a bytebeat and print it out:
func TestByteBeat(tt *testing.T) {
data := ByteBeat(10, 8000, func(t int) int {
return (t>>10^t>>11)
})
if strings.Contains(data, "error") {
tt.Fatalf("%s", data)
}
println(data)
}
then we can output, convert, and playback the audio:
> gno test --verbose examples/gno.land/p/demo/audio/bytebeat | \
base64 -d > bytebeat.wav
> play bytebeat.wav
.left-column[
] .right-column[
a realm needs a Render
function that can be used to render markdown to the web frontend.
it also has global persistence, so we can easily add a comment function:
var comments []Comment // global persists data without ORM
func AddComment(msg string) string {
caller := std.GetOrigCaller() // smart-contract call
comments = append(comments, Comment{
User: string(caller),
Message: msg,
})
...
}
]
.left-column[
] .right-column[
lets write some code for the bytebeat realm:
./examples/gno.land/r/demo/bytebeat/bytebeat.gno
]
.left-column[
] .right-column[
now we are finished with packages and realms, we can spin up a test net and upload them as a smart contract to interact with.
.left-column[
] .right-column[
first we will spinup a test net on our local machine to upload our package + realms.
> make run
which is a quick way to kill old servers, delete their content, and then spin up the gno.land server and web interface. i.e.:
pkill -f 'build/gnoland'
pkill -f 'build/gnoweb'
rm -rf gno.land/testdir
cd gno.land && ./build/gnoland start >/dev/null 2>&1 &
sleep 5
cd gno.land && ./build/gnoweb &
sleep 3
.left-column[
] .right-column[
here is the command for pushing the first package from the file in examples/gno.land/p/demo/audio/riff
:
gnokey maketx addpkg \
--pkgpath "gno.land/p/demo/audio/riff/v1" \
--pkgdir "examples/gno.land/p/demo/audio/riff" \
--deposit 100000000ugnot \
--gas-fee 1000000ugnot \
--gas-wanted 2000000 \
--broadcast --chainid dev --remote localhost:26657
YOURKEY
the argument pkgpath
defines how our package or realm is imported. the pkgdir
defines where it sits on the disk.
the deposit
, gas-fee
, and gas-wanted
are related to allocations needed for processing the package or realm.
be sure to change YOURKEY
to the key that you setup (gnokey list
lists all of them).
.left-column[
] .right-column[
quick note: if you change your code and want to update your realm, you can just use --pkgpath
to generate a new version.
gnokey maketx addpkg \
--pkgpath "gno.land/p/demo/audio/riff/v2" \
--pkgdir "examples/gno.land/p/demo/audio/riff" \
--deposit 100000000ugnot \
--gas-fee 1000000ugnot \
--gas-wanted 2000000 \
--broadcast --chainid dev --remote localhost:26657
YOURKEY
in this case, gno.land/p/demo/audio/riff/v1
was changed to gno.land/p/demo/audio/riff/v2
, but defined from the same directory.
.left-column[
] .right-column[
to ease pushing packages and realms during development, you can add a flag --insecure-password-stdin=true
. this way you can save the password to a file, e.g. password
and pass it in to run from a script, e.g.:
cat password | gnokey maktex addpkg \
... (same as before) ... \
--insecure-password-stdin=true YOURKEY
for now, this is encapsulated in the Makefile
when you run
KEY=YOURKEY make push
(make sure your password is saved into a local file password
).
]
.left-column[
] .right-column[
The pkgpath
for the bytebeat realm was set in the gnokey maketx
as gno.land/r/demo/bytebeat/v1
.
It is now available on the Gno.land web interface at
localhost:8888/r/demo/bytebeat/v1
check it out!
.left-column[
] .right-column[
remember we exported AddComment
? we can utilize that function with our key and the Gno.land server:
gnokey maketx call --pkgpath "gno.land/r/demo/bytebeat/v1" \
--func "AddComment" --args "hello, world" \
--gas-fee 1000000ugnot --gas-wanted 8000000 \
--broadcast --chainid dev --remote localhost:26657 \
YOURKEY
You can specify --pkgpath
to target a realm and then use --func
to specify the exported function. Arguments for the function are sequential --args
arguments.
.left-column[
] .right-column[
find more bytebeat formulas here for inspiration.
data = bytebeat.ByteBeat(seconds, 8000, func(t int) int {
return ... // <- your bytebeat function!!
})
and then upload a new realm:
gnokey maketx addpkg \
--pkgpath "gno.land/p/demo/audio/bytebeat/whatever" \
--pkgdir "examples/gno.land/r/demo/bytebeat" \
--deposit 100000000ugnot \
--gas-fee 1000000ugnot \
--gas-wanted 2000000 \
--broadcast --chainid dev --remote localhost:26657
YOURKEY
anyone can now use those packages and realms to upload their own smart contract that generates bytebeat!
]
.left-column[
] .right-column[
- continue exploring with the dozens of examples
- more information on getting started
- checkout what people are building
- read previous talks about Gno
- join the discord
.left-column[
] .right-column[
special thanks to the amazing growing Gno team - Jae (@jaekwon), Manfred (@moul), Morgan (@thehowl), Miloš (@zivkovicmilos), Antonio (@ajnavarro), Michelle, Johnny, Valeh, and so so many more!!
]