designing seamless interconnected dApps with gno
BUIDL Europe, 9 Jan 2025, Lisbon
Manfred Touron
VP Eng., gno.land
Manfred Touron
VP Eng., gno.land
package main func main() { println("Hello, BUIDL! It's Manfred.") }
package hello func Hello() string { return "hello world" }
package counter var Counter int func Inc() { Counter += 1 } func Render(path string) string { return "My Super Counter: " + Counter }
package counter import "std" var ( Counter int LastCaller std.Address ) func Inc() int { return addToCounter(1) } func Add(amount int) int { return addToCounter(amount) } func addToCounter(amount int) int { Counter += amount LastCaller = std.GetOrigCaller() return Counter }
package guest import "std" var messages avl.Tree // std.Address -> string (message) func AddMessage(message string) { caller := std.GetOrigCaller() if _, ok := messages.Get(caller); ok { panic("this user already post a message") } messages.Set(caller, message) // add message to our messages list } func Render(path string) string { var view string for _, message := range messages { view = view + "\n" + message // add message to the render } return view }
the gnovm enables:
seamless interoperability of
untrusted user programs
written in a good language
9package alice var x int func GetX() int { return x } func SetX(n int) { x = n }
package bob import "alice" func IncrAlice() { x := alice.GetX() alice.SetX(x + 1) }
package grc20 // gno.land/p/demo/grc/grc20 type Teller interface { TotalSupply() uint64 BalanceOf(account std.Address) uint64 Transfer(to std.Address, amount uint64) error Allowance(owner, spender std.Address) uint64 Approve(spender std.Address, amount uint64) error TransferFrom(from, to std.Address, amount uint64) error } type Token struct { name, symbol string decimals uint ledger *PrivateLedger } type PrivateLedger struct { totalSupply uint64 balances, allowances avl.Tree } func NewToken(name, symbol string, decs uint) (*Token, *PrivateLedger) { /*...*/ }
// https://gno.land/r/moul/x/buidl20$source package buidl20 import ( "std" "gno.land/p/demo/grc/grc20" ) // Token: public safe object for composability // adm: privileged object for minting var Token, adm = grc20.NewToken("Buidl", "BDL", 4) // grc20 API for the caller var UserTeller = Token.CallerTeller() func init() { adm.Mint(1_000_000, std.GetOrigCaller()) // mint 1M to the contract deployer. } // optional helpers // [...]
// gno.land/r/demo/grc20reg package grc20reg var registry = avl.NewTree() // rlmPath -> TokenGetter func Register(tokenGetter grc20.TokenGetter) { rlmPath := std.PrevRealm().PkgPath() registry.Set(rlmPath, tokenGetter) } func Get(key string) grc20.TokenGetter { tokenGetter, _ := registry.Get(key) return tokenGetter }
// gno.land/r/demo/grc20factory package grc20factory var instances avl.Tree // symbol -> instance type instance struct { token *grc20.Token ledger *grc20.PrivateLedger admin *ownable.Ownable faucet uint64 // per-request amount. disabled if 0. } func New(name, symbol string, decimals uint, initialMint, faucet uint64) { caller := std.PrevRealm().Addr() token, ledger := grc20.NewToken(name, symbol, decimals) if initialMint > 0 { ledger.Mint(admin, initialMint) } owner := ownable.NewWithAddress(admin) inst := instance{token: token, ledger: ledger, admin: owner, faucet: faucet} instances.Set(symbol, &inst) grc20reg.Register(token.Getter(), symbol) }
// Solidity example for ERC721Receiver check interface IERC721Receiver { function onERC721Received( address operator, address from, uint256 tokenId, bytes calldata data ) external returns (bytes4); } // Solidity relies on checking bytes4 signature require( contract.onERC721Received.selector == bytes4(keccak256("onERC721Received(address,address,uint256,bytes)")), "Invalid ERC721 receiver" );
package dex func (dex *DEX) PlaceOrder(tokenFrom, tokenTo grc20.Token, amount uint64, isBuy bool) int { trader, contract := std.PrevRealm().Addr(), std.CurrentRealm().Addr() userBanker := grc20.AccountBanker(tokenFrom, "") allowance := userBanker.Allowance(trader, contract) require(allowance >= amount, "insufficient allowance") err := userBanker.TransferFrom(trader, contract, amount) checkErr(err, "cannot retrieve tokens from allowance") order := &Order{trader: trader, tokenFrom: tokenFrom, tokenTo: tokenTo, amount: amount, isBuy: isBuy} dex.Append(order) std.Emit( "order_placed", "trader", trader.String(), "tokenFrom", tokenFrom.GetName(), "tokenTo", tokenTo.GetName(), "amount", ufmt.Sprintf("%d", amount), ) return dex.matchPairOrders(tokenFrom, tokenTo) }
// prop1.gno import "gno.land/r/gov/dao" import "gno.land/r/buidl/buidl20" func init() { closure := func() error { // this closure will preserve the execution context at the time of // creation, even if the proposal is executed by someone else. return executor() } prop := dao.ProposalRequest{ Title: "", Description: "lorem ipsum dolor sit amet", Executor: closure, } dao.Propose(prop) } func executor() error { buidl20.TransferTo("g12345678", 1_000_000) // transfer 1M $BUIDL return nil }
// gno.land/p/dao package dao type DAO interface { // Core proposal operations Propose(def PropDefinition) (Proposal, error) GetProposal(proposalID uint64) (Proposal, error) Execute(proposalID uint64) error // List operations ActiveProposals() PropList ArchivedProposals() PropList Len() int }
package foodao import "gno.land/p/dao" // interface // FooDAO implements the dao.DAO interface. type FooDAO struct { proposals []dao.Proposal, voters []std.Address } func (d *FooDAO) Propose(def PropDefinition) (Proposal, error) { /* [...] */ } func (d *FooDAO) GetProposal(proposalID uint64) (Proposal, error) { /* [...] */ } func (d *FooDAO) Execute(proposalID uint64) error { /* [...] */ } func (d *FooDAO) ActiveProposals() PropList { /* [...] */ } func (d *FooDAO) ArchivedProposals() PropList { /* [...] */ } func (d *FooDAO) Len() int { /* [...] */ }
package aggregateddao import "gno.land/p/dao" type AggregatedDAO struct { dao1, dao2 dao.DAO } func (a *AggregatedDAO) Propose(def dao.PropDefinition) (dao.Proposal, error) { prop1, _ := a.dao1.Propose(def) prop2, _ := a.dao2.Propose(def) // [...] } func (a *AggregatedDAO) Len() int { return a.dao1.Len() + a.dao2.Len() } func (a *AggregatedDAO) ActiveProposals() dao.PropList { return append(a.dao1.ActiveProposals(), a.dao2.ActiveProposals()...) } func (a *AggregatedDAO) ArchivedProposals() dao.PropList { return append(a.dao1.ArchivedProposals(), a.dao2.ArchivedProposals()...) } // [...]
// gno.land/r/foobardao package foobardao import ( "gno.land/r/foodao" "gno.land/r/bardao" "gno.land/p/aggregateddao" ) var FooBarDAO = aggregateddao.New(foodao.DAO, bardao.DAO)
thank you!
27