Skip to content

Commit

Permalink
feat: add r/foo20-airdrop
Browse files Browse the repository at this point in the history
  • Loading branch information
albttx committed Oct 18, 2023
1 parent 89428c5 commit 2588613
Show file tree
Hide file tree
Showing 11 changed files with 363 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package main
95 changes: 95 additions & 0 deletions examples/gno.land/p/demo/airdrop/merkle-airdrop.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package airdrop

import (
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"std"

"gno.land/p/demo/avl"
"gno.land/p/demo/grc/grc20"
"gno.land/p/demo/merkle"
"gno.land/r/demo/foo20"
"gno.land/r/demo/users"
)

var (
ErrAlreadyClaimed = errors.New("already claimed")
ErrInvalidProof = errors.New("invalid merkle proof")
)

type AirdropData struct {
Address std.Address
// TODO: use std.Coin
Amount uint64
// Amount std.Coin
}

func (data AirdropData) Bytes() []byte {
// TODO: use binary.Write
// var buf bytes.Buffer
// binary.Write(&buf, binary.BigEndian, d)
// return buf.Bytes()
// OR: use json.Marshal for frontend compatibilities

s := fmt.Sprintf("%v", data)
return []byte(s)
}

type MerkleAirdrop struct {
root string

token grc20.IGRC20
claimed *avl.Tree
}

func NewMerkleAirdrop(merkleroot string, token grc20.IGRC20) *MerkleAirdrop {
return &MerkleAirdrop{
root: merkleroot,

token: token,
claimed: avl.NewTree(),
}
}

func (ma *MerkleAirdrop) Root() string {
return ma.root
}

func (ma *MerkleAirdrop) Claim(data AirdropData, proofs []merkle.Node) error {
shasum := sha256.Sum256(data.Bytes())
hash := hex.EncodeToString(shasum[:])

if ma.claimed.Has(hash) {
return ErrAlreadyClaimed
}

if !merkle.Verify(ma.root, data, proofs) {
return ErrInvalidProof
}

// THIS WORKS
// foo20.Transfer(users.AddressOrName(data.Address), data.Amount)
// foo20.GRC20().Transfer(data.Address, data.Amount)

// THIS DOES NOT WORK
// err := ma.token.Transfer(data.Address, data.Amount)
// if err != nil {
// return err
// }

ma.claimed.Set(hash, data.Amount)
return nil
}

func (ma MerkleAirdrop) TotalClaimed() uint64 {
var claimed uint64 = 0

ma.claimed.Iterate("", "", func(k string, v interface{}) bool {
claimed += v.(uint64)
return false
})

return claimed
}
100 changes: 100 additions & 0 deletions examples/gno.land/p/demo/airdrop/merkle-airdrop_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package airdrop

import (
"std"
"testing"

"gno.land/p/demo/grc/grc20"
"gno.land/p/demo/merkle"
"gno.land/r/demo/foo20"
"gno.land/r/demo/users"
)

var leaves []merkle.Hashable = []AirdropData{
{
Address: "g1auhc2cymv7gn9qmls0ttdr3wqrljgz0dhq90e",
Amount: 10000,
},
{
Address: "g1zyvskpxg5lv4qpygtuvp93zprrrjpk2exa9rfx",
Amount: 10000,
},
{
Address: "g14szvkruznx49sxe4m9dmg3m8606sm6yp4a0wv8",
Amount: 10000,
},
}

func TestRegisterMerkle(t *testing.T) {
tree := merkle.NewTree(leaves)
root := tree.Root()
contractAddr := std.DerivePkgAddr("gno.land/r/demo/tok20-airdrop")

token := grc20.NewAdminToken("TOKEN", "TOK", 6)
token.Mint(contractAddr, 50000) // Airdrop contract

tok20airdrop := NewMerkleAirdrop(root, token.GRC20())
}

func TestClaimAirdrop(t *testing.T) {
contractAddr := std.DerivePkgAddr("gno.land/r/demo/tok20-airdrop")
std.TestSetOrigCaller(contractAddr)

// instantiate foo20 airdrop contract
tree := merkle.NewTree(leaves)
root := tree.Root()

token := grc20.NewAdminToken("TOKEN", "TOK", 6)
token.Mint(contractAddr, 50000) // Airdrop contract

tok20airdrop := NewMerkleAirdrop(root, token.GRC20())

sumClaimed := uint64(0)
for _, leaf := range leaves {
data := leaf.(AirdropData)
user := data.Address
sumClaimed += data.Amount

proofs, err := tree.Proof(leaf)
if err != nil {
t.Fatalf("failed to generate proof, %v", err)
return
}

// claim airdrop
tok20airdrop.Claim(data, proofs)
}

ttClaimed := tok20airdrop.TotalClaimed()
if ttClaimed != sumClaimed {
t.Fatalf("expected: %d, got: %d", sumClaimed, ttClaimed)
}
}

func TestDoubleClaim(t *testing.T) {
contractAddr := std.DerivePkgAddr("gno.land/r/demo/tok20-airdrop")
std.TestSetOrigCaller(contractAddr)

tree := merkle.NewTree(leaves)
token := grc20.NewAdminToken("TOKEN", "TOK", 6)
token.Mint(contractAddr, 50000)

tok20airdrop := NewMerkleAirdrop(tree.Root(), token.GRC20())

leaf := leaves[0]
proofs, err := tree.Proof(leaf)
if err != nil {
t.Fatalf("failed to generate proof, %v", err)
return
}

err = tok20airdrop.Claim(leaf.(AirdropData), proofs)
if err != nil {
t.Fatalf("failed to claim airdrop: %v", err)
}

err = tok20airdrop.Claim(leaf.(AirdropData), proofs)
if err != ErrAlreadyClaimed {
t.Fatalf("want: %v, got: %v", ErrAlreadyClaimed, err)
}
}
10 changes: 9 additions & 1 deletion examples/gno.land/p/demo/grc/grc20/admin_token.gno
Original file line number Diff line number Diff line change
Expand Up @@ -262,5 +262,13 @@ func (t *AdminToken) RenderHome() string {

// GRC20 returns an instance that can be exposed to the end user.
func (t *AdminToken) GRC20() IGRC20 {
return &userToken{admin: t}
// println("GRC20: Setting realm to1", std.CurrentRealm())
println("1. ====")
println("GRC20: Setting realm to2", std.PrevRealm())
println("2. ====")
return &userToken{
admin: t,
// realm: std.CurrentRealm(),
realm: std.PrevRealm(),
}
}
12 changes: 9 additions & 3 deletions examples/gno.land/p/demo/grc/grc20/user_token.gno
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ type userToken struct {
IGRC20 // implements the GRC20 interface.

admin *AdminToken

realm std.Realm
}

// IGRC20 implementation.
Expand All @@ -27,7 +29,11 @@ func (t *userToken) BalanceOf(owner std.Address) (uint64, error) {
}

func (t *userToken) Transfer(to std.Address, amount uint64) error {
caller := std.PrevRealm().Addr()
// caller := std.PrevRealm().Addr()
caller := t.realm.Addr()
println(
"===> transfer:", amount, "from:", caller, "to:", to,
)
return t.admin.transfer(caller, to, amount)
}

Expand All @@ -36,12 +42,12 @@ func (t *userToken) Allowance(owner, spender std.Address) (uint64, error) {
}

func (t *userToken) Approve(spender std.Address, amount uint64) error {
caller := std.PrevRealm().Addr()
caller := t.realm.Addr()
return t.admin.approve(caller, spender, amount)
}

func (t *userToken) TransferFrom(from, to std.Address, amount uint64) error {
spender := std.PrevRealm().Addr()
spender := t.realm.Addr()
if err := t.admin.spendAllowance(from, spender, amount); err != nil {
return err
}
Expand Down
50 changes: 50 additions & 0 deletions examples/gno.land/r/demo/foo20-airdrop/airdrop.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package foo20airdrop

import (
"std"

"gno.land/p/demo/airdrop"
"gno.land/p/demo/grc/grc20"
"gno.land/p/demo/merkle"
"gno.land/r/demo/foo20"
)

var (
token grc20.IGRC20 = foo20.GRC20()

// admin std.Address = "g1sw5xklxjjuv0yvuxy5f5s3l3mnj0nqq626a9wr" // albttx.gno

foo20airdrop *airdrop.MerkleAirdrop
)

func PrevRealm() std.Realm {
return std.PrevRealm()
}

func RegisterMerkleRoot(root string) {
if foo20airdrop != nil {
panic("foo20 airdrop merkle root is already registered")
}
foo20airdrop = airdrop.NewMerkleAirdrop(root, token)
}

func Claim(data airdrop.AirdropData, proofs []merkle.Node) {
err := foo20airdrop.Claim(data, proofs)
if err != nil {
panic(err.Error())
}

// err = token.Transfer(data.Address, data.Amount)
// if err != nil {
// panic(err.Error())
// }
}

func TotalClaimed() uint64 {
return foo20airdrop.TotalClaimed()
}

// for tests purpose
func reset() {
foo20airdrop = nil
}
70 changes: 70 additions & 0 deletions examples/gno.land/r/demo/foo20-airdrop/airdrop_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package foo20airdrop

import (
"std"
"testing"

"gno.land/p/demo/airdrop"
"gno.land/p/demo/merkle"
"gno.land/r/demo/foo20"
"gno.land/r/demo/users"
)

var leaves []merkle.Hashable = []airdrop.AirdropData{
{
Address: "g1auhc2cymv7gn9qmls0ttdr3wqrljgz0dhq90e",
Amount: 1_000_000,
},
{
Address: "g1zyvskpxg5lv4qpygtuvp93zprrrjpk2exa9rfx",
Amount: 1_000_000,
},
{
Address: "g14szvkruznx49sxe4m9dmg3m8606sm6yp4a0wv8",
Amount: 1_000_000,
},
}

// func TestRegisterMerkle(t *testing.T) {
// tree := merkle.NewTree(leaves)
// root := tree.Root()

// RegisterMerkleRoot(root)
// reset()
// }

func TestClaimAirdrop(t *testing.T) {
// println("prev", std.PrevRealm())
// println("current", std.CurrentRealm())
// println("foo20-airdrop.PrevRealm", PrevRealm())

contractAddr := std.DerivePkgAddr("gno.land/r/demo/foo20-airdrop")
println("contractADDR", contractAddr)
std.TestSetOrigCaller(contractAddr)

// instantiate foo20 airdrop contract
tree := merkle.NewTree(leaves)
RegisterMerkleRoot(tree.Root())
defer reset()

sumClaimed := uint64(0)
for _, leaf := range leaves {
data := leaf.(airdrop.AirdropData)
user := data.Address
sumClaimed += data.Amount

proofs, err := tree.Proof(leaf)
if err != nil {
t.Fatalf("failed to generate proof, %v", err)
return
}

// claim airdrop
Claim(leaf.(airdrop.AirdropData), proofs)
}

ttClaimed := TotalClaimed()
if ttClaimed != sumClaimed {
t.Fatalf("expected: %d", sumClaimed)
}
}
1 change: 1 addition & 0 deletions examples/gno.land/r/demo/foo20-airdrop/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module gno.land/r/demo/foo20-airdrop
10 changes: 8 additions & 2 deletions examples/gno.land/r/demo/foo20/foo20.gno
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,14 @@ var (

func init() {
foo = grc20.NewAdminToken("Foo", "FOO", 4)
foo.Mint(admin, 1000000*10000) // @administrator (1M)
foo.Mint("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq", 10000*10000) // @manfred (10k)
foo.Mint(admin, 10_000_000_000) // @administrator (1M)
foo.Mint("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq", 100_000_000) // @manfred (10k)
foo.Mint(std.DerivePkgAddr("gno.land/r/demo/foo20-airdrop"), 10_000_000)
// foo.Mint(std.DerivePkgAddr("user1.gno"), 10_000_000)
}

func GRC20() grc20.IGRC20 {
return foo.GRC20()
}

// method proxies as public functions.
Expand Down
Loading

0 comments on commit 2588613

Please sign in to comment.