Skip to content

Commit

Permalink
feat: r/demo/foo20-airdrop
Browse files Browse the repository at this point in the history
  • Loading branch information
albttx committed Jun 16, 2023
1 parent 2937a4c commit 4b40869
Show file tree
Hide file tree
Showing 8 changed files with 319 additions and 13 deletions.
6 changes: 6 additions & 0 deletions examples/gno.land/p/demo/airdrop/airdrop.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package airdrop

type Airdrop interface {
Claim(amount uint64, proof []string)
TotalClaimed() uint64
}
101 changes: 101 additions & 0 deletions examples/gno.land/p/demo/airdrop/airdrop_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package airdrop

import (
"std"
"testing"

"gno.land/p/demo/merkle"
"gno.land/p/demo/grc/grc20"
"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)
}
}
89 changes: 89 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,89 @@
package airdrop

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

"gno.land/p/demo/avl"
"gno.land/p/demo/grc/grc20"
"gno.land/p/demo/merkle"
"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
}

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
}
6 changes: 3 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 @@ -27,7 +27,7 @@ func (t *userToken) BalanceOf(owner std.Address) (uint64, error) {
}

func (t *userToken) Transfer(to std.Address, amount uint64) error {
owner := std.GetOrigCaller()
owner := std.PrevRealm().Addr()
return t.admin.transfer(owner, to, amount)
}

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

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

func (t *userToken) TransferFrom(from, to std.Address, amount uint64) error {
spender := std.GetOrigCaller()
spender := std.PrevRealm().Addr()
if err := t.admin.spendAllowance(from, spender, amount); err != nil {
return err
}
Expand Down
39 changes: 39 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,39 @@
package foo20airdrop

import (
"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 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())
}
}

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

// for tests purpose
func reset() {
foo20airdrop = nil
}
65 changes: 65 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,65 @@
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) {
contractAddr := std.DerivePkgAddr("gno.land/r/demo/foo20-airdrop")
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)
}
}
22 changes: 14 additions & 8 deletions examples/gno.land/r/demo/foo20/foo20.gno
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,21 @@ 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.GRC20()
}

// method proxies as public functions.
//

// getters.

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

func TotalSupply() uint64 {
return foo.TotalSupply()
}
Expand All @@ -48,17 +54,17 @@ func Allowance(owner, spender users.AddressOrName) uint64 {
// setters.

func Transfer(to users.AddressOrName, amount uint64) {
caller := std.GetOrigCaller()
caller := std.PrevRealm().Addr()
foo.Transfer(caller, to.Resolve(), amount)
}

func Approve(spender users.AddressOrName, amount uint64) {
caller := std.GetOrigCaller()
caller := std.PrevRealm().Addr()
foo.Approve(caller, spender.Resolve(), amount)
}

func TransferFrom(from, to users.AddressOrName, amount uint64) {
caller := std.GetOrigCaller()
caller := std.PrevRealm().Addr()
foo.TransferFrom(caller, from.Resolve(), to.Resolve(), amount)
}

Expand All @@ -67,20 +73,20 @@ func TransferFrom(from, to users.AddressOrName, amount uint64) {
func Faucet() {
// FIXME: add limits?
// FIXME: add payment in gnot?
caller := std.GetOrigCaller()
caller := std.PrevRealm().Addr()
foo.Mint(caller, 1000*10000) // 1k
}

// administration.

func Mint(address users.AddressOrName, amount uint64) {
caller := std.GetOrigCaller()
caller := std.PrevRealm().Addr()
assertIsAdmin(caller)
foo.Mint(address.Resolve(), amount)
}

func Burn(address users.AddressOrName, amount uint64) {
caller := std.GetOrigCaller()
caller := std.PrevRealm().Addr()
assertIsAdmin(caller)
foo.Burn(address.Resolve(), amount)
}
Expand Down
Loading

0 comments on commit 4b40869

Please sign in to comment.