-
Notifications
You must be signed in to change notification settings - Fork 373
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
363 additions
and
8 deletions.
There are no files selected for viewing
1 change: 1 addition & 0 deletions
1
examples/gno.land/p/demo/airdrop/grc20_merkle/merkle-airdrop.gno
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
package main |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
100
examples/gno.land/p/demo/airdrop/merkle-airdrop_test.gno
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
module gno.land/r/demo/foo20-airdrop |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.