Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Airdrop test #11

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 45 additions & 3 deletions airdrop/airdrop.gno
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ package airdrop
import (
"errors"
"std"
"strconv"
"strings"
"time"

"gno.land/p/demo/grc/grc20"
"gno.land/p/demo/uint256"

"gno.land/p/demo/ufmt"
u256 "gno.land/p/demo/uint256"
"gno.land/r/governance/snapshot"
)

Expand All @@ -26,7 +28,7 @@ var timeNow = time.Now
type Claim struct {
ID uint64
Claimee std.Address
Amount *uint256.Uint
Amount *u256.Uint
}

// Config represents the airdrop configuration
Expand Down Expand Up @@ -205,3 +207,43 @@ func (a *Airdrop) IsClaimedAtSnapshot(snapshotID uint64, claimID uint64) (bool,
func (a *Airdrop) GetSnapshot(id uint64) (snapshot.Snapshot, error) {
return a.snapshotManager.GetSnapshot(id)
}

// ParseAndProcessAirdrop parses airdrop data in the format "address=amount" and processes it
func ParseAndProcessAirdrop(data string) (address std.Address, amount *u256.Uint, err error) {
parts := strings.Split(data, "=")
if len(parts) != 2 {
// panic("Invalid airdrop data format")
return "", nil, ufmt.Errorf("Invalid airdrop data format")
}

address = std.Address(parts[0])
amountStr := strings.TrimSuffix(parts[1], "ugnot")

amount, err = u256.FromDecimal(amountStr)
if err != nil {
return "", nil, ufmt.Errorf("Invalid amount: %s", err.Error())
}

return address, amount, nil
}

// ProcessAirdropFile processes the entire airdrop file content
func ProcessAirdropFile(fileContent string) (map[std.Address]*u256.Uint, error) {
lines := strings.Split(fileContent, "\n")
result := make(map[std.Address]*u256.Uint)

for _, line := range lines {
if line == "" {
continue
}

address, amount, err := ParseAndProcessAirdrop(line)
if err != nil {
return nil, ufmt.Errorf("Error processing line '%s': %v", line, err)
}

result[address] = amount
}

return result, nil
}
173 changes: 167 additions & 6 deletions airdrop/airdrop_test.gno
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ package airdrop
import (
"errors"
"std"
"strings"
"testing"
"time"

"gno.land/p/demo/grc/grc20"
"gno.land/p/demo/merkle"
"gno.land/p/demo/testutils"
"gno.land/p/demo/ufmt"
"gno.land/p/demo/uint256"
u256 "gno.land/p/demo/uint256"
)

var testAddress = testutils.TestAddress("test")
Expand Down Expand Up @@ -149,7 +150,7 @@ func TestClaim(t *testing.T) {
claim := Claim{
ID: 1,
Claimee: claimeeAddress,
Amount: uint256.NewUint(100000),
Amount: u256.NewUint(100000),
}

// Test successful claim
Expand Down Expand Up @@ -184,7 +185,7 @@ func TestClaim(t *testing.T) {
bigClaim := Claim{
ID: 2,
Claimee: claimeeAddress,
Amount: uint256.NewUint(1000000), // More than the remaining balance
Amount: u256.NewUint(1000000), // More than the remaining balance
}

defer func() {
Expand Down Expand Up @@ -213,7 +214,7 @@ func TestClaim64(t *testing.T) {
claims[i] = Claim{
ID: uint64(i * 64), // Ensure first claim ID is multiple of 64
Claimee: std.Address(ufmt.Sprintf("claimee%d", i)),
Amount: uint256.NewUint(1000), // Each claim is for 1000 tokens
Amount: u256.NewUint(1000), // Each claim is for 1000 tokens
}
}

Expand Down Expand Up @@ -247,7 +248,7 @@ func TestClaim64(t *testing.T) {
bigClaims[i] = Claim{
ID: uint64((i + 1) * 64),
Claimee: std.Address(ufmt.Sprintf("claimee%d", i)),
Amount: uint256.NewUint(20000), // Each claim is for 20000 tokens, which is more than the remaining balance
Amount: u256.NewUint(20000), // Each claim is for 20000 tokens, which is more than the remaining balance
}
}

Expand Down Expand Up @@ -387,7 +388,7 @@ func TestAirdropWithSnapshot(t *testing.T) {
claims[i] = Claim{
ID: uint64(i * 64), // Ensure first claim ID is multiple of 64
Claimee: testutils.TestAddress(ufmt.Sprintf("claimee%d", i)),
Amount: uint256.NewUint(1000), // Each claim is for 1000 tokens
Amount: u256.NewUint(1000), // Each claim is for 1000 tokens
}
}

Expand Down Expand Up @@ -442,3 +443,163 @@ func TestAirdropWithSnapshot(t *testing.T) {
t.Errorf("Expected remaining tokens %d, got %d", expectedRemainingTokens, snapshot.RemainingTokens)
}
}

func TestParseAirdropData(t *testing.T) {
tests := []struct {
name string
input string
expectedAddr string
expectedAmount string
expectError bool
}{
{
name: "Valid input",
input: "g1p3ucd3ptpw902fluyjzhq3ffgq4ntddatev7s5=42027010477582ugnot",
expectedAddr: "g1p3ucd3ptpw902fluyjzhq3ffgq4ntddatev7s5",
expectedAmount: "42027010477582",
expectError: false,
},
{
name: "Valid input without ugnot suffix",
input: "g1p3ucd3ptpw902fluyjzhq3ffgq4ntddatev7s5=42027010477582",
expectedAddr: "g1p3ucd3ptpw902fluyjzhq3ffgq4ntddatev7s5",
expectedAmount: "42027010477582",
expectError: false,
},
{
name: "Invalid format",
input: "invalid_format",
expectError: true,
},
{
name: "Invalid amount",
input: "g1p3ucd3ptpw902fluyjzhq3ffgq4ntddatev7s5=invalid_amount",
expectError: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
addr, amount, err := ParseAndProcessAirdrop(tt.input)

if tt.expectError {
if err == nil {
t.Errorf("Expected an error, but got none")
}
} else {
if err != nil {
t.Errorf("Unexpected error: %v", err)
}

if addr.String() != tt.expectedAddr {
t.Errorf("Expected address %s, but got %s", tt.expectedAddr, addr)
}

expectedAmount, _ := u256.FromDecimal(tt.expectedAmount)
if amount.Cmp(expectedAmount) != 0 {
t.Errorf("Expected amount %s, but got %s", expectedAmount.ToString(), amount.ToString())
}
}
})
}
}

// region airdrop distribution tests

func TestProcessAirdropFile(t *testing.T) {
airdropData := `g1p3ucd3ptpw902fluyjzhq3ffgq4ntddatev7s5=42027010477582ugnot
g14lultfckehtszvzw4ehu0apvsr77afvyy5u50n=35247706217215ugnot
g15hmqrc245kryaehxlch7scl9d9znxa58n2a3c0=30417901501668ugnot
g1nm0rrq86ucezaf8uj35pq9fpwr5r82cl5vyaqs=28489219597175ugnot
g1x54ltnyg88k0ejmk8ytwrhd3ltm84xehs0rn7d=22983213987686ugnot
g1cj7u0wpe45j0udnsy306sna7peah054uj6h4rj=18441664084402ugnot
g1z8mzakma7vnaajysmtkwt4wgjqr2m84t3sc2hx=18346174582525ugnot
g1dtq0y9reqst7d99fd3c7x6dflh4eazm4ypmrpn=16523265241754ugnot`

expectedResults := make(map[std.Address]*u256.Uint)
for _, line := range strings.Split(airdropData, "\n") {
addr, amount, err := ParseAndProcessAirdrop(line)
if err != nil {
t.Fatalf("Error parsing test data: %v", err)
}
println(ufmt.Sprintf("addr: %s, amount: %s", addr.String(), amount.ToString()))
expectedResults[addr] = amount
}

result, err := ProcessAirdropFile(airdropData)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}

expectedLength := len(expectedResults)
if len(result) != expectedLength {
t.Errorf("Expected %d airdrop entries, got %d", expectedLength, len(result))
}

// Check a few specific entries
checkEntry := func(addr string, expectedAmount string) {
amount, ok := result[std.Address(addr)]
if !ok {
t.Errorf("Address %s not found in result", addr)
return
}
expected, _ := u256.FromDecimal(expectedAmount)
if amount.Cmp(expected) != 0 {
t.Errorf("For address %s, expected amount %s, got %s", addr, expected.ToString(), amount.ToString())
}
}

checkEntry("g1p3ucd3ptpw902fluyjzhq3ffgq4ntddatev7s5", "42027010477582")
checkEntry("g1dtq0y9reqst7d99fd3c7x6dflh4eazm4ypmrpn", "16523265241754")

for addr, expectedAmount := range expectedResults {
actualAmount, ok := result[addr]
if !ok {
t.Errorf("Address %s not found in result", addr)
continue
}
if actualAmount.Cmp(expectedAmount) != 0 {
t.Errorf("For address %s, expected amount %s, got %s", addr, expectedAmount.ToString(), actualAmount.ToString())
}
}
}

// func TestAirdropDistribution(t *testing.T) {
// token := NewMockGRC20("Test Token", "TEST", 18)

// airdropData := `g1p3ucd3ptpw902fluyjzhq3ffgq4ntddatev7s5=42027010477582ugnot
// g14lultfckehtszvzw4ehu0apvsr77afvyy5u50n=35247706217215ugnot
// g15hmqrc245kryaehxlch7scl9d9znxa58n2a3c0=30417901501668ugnot
// g1nm0rrq86ucezaf8uj35pq9fpwr5r82cl5vyaqs=28489219597175ugnot`

// config := Config{
// RefundableTimestamp: 0,
// RefundTo: testutils.TestAddress("refund_to"),
// }

// airdropAddr := testutils.TestAddress("airdrop")
// airdrop := NewAirdrop(token, config, airdropAddr)

// totalSupply := uint64(1000000000000000)
// token.balances[airdropAddr] = totalSupply

// claims, err := ProcessAirdropFile(airdropData)
// if err != nil {
// t.Fatalf("Error processing airdrop data: %v", err)
// }

// var counter uint64
// for addr, amount := range claims {
// amount256 := u256.FromDecimal()
// claim := Claim{
// ID: counter,
// Claimee: std.Address(addr),
// Amount: u256.FromDecimal(amount),
// }
// _, err := airdrop.Claim(claim)
// if err != nil {
// t.Fatalf("Error claiming airdrop: %v", err)
// }
// counter += 1
// }
// }