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 Getters #19

Merged
merged 10 commits into from
Aug 26, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
125 changes: 125 additions & 0 deletions airdrop/getter.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package airdrop

import (
"std"
"strconv"
"strings"

"gno.land/p/demo/json"
"gno.land/p/demo/ufmt"
"gno.land/p/demo/uint256"
)

// GetAirdropConfig returns the current configuration of the airdrop as a JSON string.
//
// Example:
//
// {
// "refundable_timestamp": "1234567890",
// "refund_to": "g1234567890abcdefghijklmnop"
// }
func (a *Airdrop) GetAirdropConfig() string {
r3v4s marked this conversation as resolved.
Show resolved Hide resolved
node := json.ObjectNode("", nil)

timestamp := strconv.FormatUint(a.config.RefundableTimestamp, 10)
err := node.AppendObject("refundable_timestamp", json.StringNode("refundable_timestamp", timestamp))
r3v4s marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
panic(err)
}

err = node.AppendObject("refund_to", json.StringNode("refund_to", a.config.RefundTo.String()))
if err != nil {
panic(err)
}

return marshal(node)
}

// GetTotalClaims returns the total number of claims made so far as a JSON string.
//
// Example:
//
// {
// "total_claims": "16"
// }
func (a *Airdrop) GetTotalClaims() string {
r3v4s marked this conversation as resolved.
Show resolved Hide resolved
totalClaims := 0
for _, bitmap := range a.claimedBitmap {
totalClaims += popCount(bitmap)
}

node := json.ObjectNode("", nil)

claims := strconv.Itoa(totalClaims)
err := node.AppendObject("total_claims", json.StringNode("total_claims", claims))
if err != nil {
panic(err)
}

return marshal(node)
}

// GetRemainingBalance returns the remaining balance of tokens available for airdrop.
//
// Example:
//
// {
// "remaining_balance": "1000000"
// }
func (a *Airdrop) GetRemainingBalance() string {
r3v4s marked this conversation as resolved.
Show resolved Hide resolved
balance := a.token.BalanceOf(a.address)

node := json.ObjectNode("", nil)

balanceStr := uint256.NewUint(balance).ToString()
err := node.AppendObject("remaining_balance", json.StringNode("remaining_balance", balanceStr))
if err != nil {
panic(err)
}

return marshal(node)
}

// GetClaimStatus returns the status for a given claim ID.
//
// Example:
//
// {
// "claim_id": "123",
// "claimed": true
// }
func (a *Airdrop) GetClaimStatus(claimID uint64) string {
r3v4s marked this conversation as resolved.
Show resolved Hide resolved
claimed := a.IsClaimed(claimID)

node := json.ObjectNode("", nil)

id := strconv.FormatUint(claimID, 10)
err := node.AppendObject("claim_id", json.StringNode("claim_id", id))
if err != nil {
panic(err)
}
err = node.AppendObject("claimed", json.BoolNode("claimed", claimed))
if err != nil {
panic(err)
}

return marshal(node)
}

// popCount returns the number of set bits in a uint64
func popCount(x uint64) int {
count := 0
for x != 0 {
count++
x &= x - 1
}
return count
}

func marshal(node *json.Node) string {
b, err := json.Marshal(node)
if err != nil {
panic(ufmt.Sprintf("failed to marshal JSON: %v", err))
}
return string(b)
}
192 changes: 192 additions & 0 deletions airdrop/getter_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
package airdrop

import (
"std"
"strconv"
"testing"

"gno.land/p/demo/json"
"gno.land/p/demo/testutils"
u256 "gno.land/p/demo/uint256"
)

func _setupAirdropConfig(t *testing.T) (*Airdrop, std.Address) {
mockToken := NewMockGRC20("Test Token", "TST", 18)
addr := testutils.TestAddress("addr")
config := Config{
RefundableTimestamp: 1234567890,
RefundTo: addr,
}
airdrop := NewAirdrop(mockToken, config, addr)

return airdrop, addr
}

func TestGetAirdropConfig(t *testing.T) {
airdrop, refundTo := _setupAirdropConfig(t)

jsonStr := airdrop.GetAirdropConfig()

node, err := json.Unmarshal([]byte(jsonStr))
if err != nil {
t.Fatalf("Failed to unmarshal JSON: %v", err)
}

timestamp, _ := node.MustKey("refundable_timestamp").Value()
if timestamp != "1234567890" {
t.Errorf("Expected RefundableTimestamp to be 1234567890, but got %v", timestamp)
}

refund, _ := node.MustKey("refund_to").Value()
if refund != refundTo.String() {
t.Errorf("Expected RefundTo to be %s, but got %v", refundTo.String(), refund)
}

if refund != refundTo.String() {
t.Errorf("Expected RefundTo to be %s, but got %v", refundTo.String(), refund)
}
}

func TestGetTotalClaims(t *testing.T) {
ardp, _ := _setupAirdropConfig(t)

ardp.claimedBitmap[0] = 0b10101010
ardp.claimedBitmap[1] = 0b01010101
ardp.claimedBitmap[2] = 0b11111111

jsonStr := ardp.GetTotalClaims()

node, err := json.Unmarshal([]byte(jsonStr))
if err != nil {
t.Fatalf("Failed to unmarshal JSON: %v", err)
}

totalClaims, _ := node.MustKey("total_claims").Value()
if totalClaims != "16" {
t.Errorf("Expected TotalClaims to be 16, but got %v", totalClaims)
}
}

func TestGetRemainingBalance(t *testing.T) {
mockToken := NewMockGRC20("Test Token", "TST", 18)

airdropAddress := testutils.TestAddress("airdrop_address")
airdrop := NewAirdrop(mockToken, Config{}, airdropAddress)

// Set up some initial balance
initialBalance := uint64(1000000)
mockToken.balances[airdropAddress] = initialBalance

jsonStr := airdrop.GetRemainingBalance()
node, err := json.Unmarshal([]byte(jsonStr))
if err != nil {
t.Fatalf("Failed to unmarshal JSON: %v", err)
}

remainingBalance, _ := node.MustKey("remaining_balance").GetString()
if remainingBalance != "1000000" {
t.Errorf("Expected remaining balance to be 1000000, but got %v", remainingBalance)
}

actualBalance, err := u256.FromDecimal(remainingBalance)
if err != nil {
t.Fatalf("Failed to convert remaining balance string to uint256: %v", err)
}

expectedBalance := u256.NewUint(initialBalance)
if actualBalance.Cmp(expectedBalance) != 0 {
t.Errorf(
"Expected remaining balance to be %s, but got %s",
expectedBalance.ToString(),
actualBalance.ToString(),
)
}

// Simulate some claims
claimedAmount := uint64(300000)
mockToken.balances[airdropAddress] -= claimedAmount

jsonStr = airdrop.GetRemainingBalance()

updatedNode, err := json.Unmarshal([]byte(jsonStr))
if err != nil {
t.Fatalf("Failed to unmarshal updated JSON: %v", err)
}

updatedRemainingBalance, _ := updatedNode.MustKey("remaining_balance").GetString()
if updatedRemainingBalance != "700000" {
t.Errorf("Expected updated remaining balance to be 700000, but got %v", updatedRemainingBalance)
}

updatedActualBalance, err := u256.FromDecimal(updatedRemainingBalance)
if err != nil {
t.Fatalf("Failed to convert updated remaining balance string to uint256: %v", err)
}

updatedExpectedBalance := u256.NewUint(initialBalance - claimedAmount)
if updatedActualBalance.Cmp(updatedExpectedBalance) != 0 {
t.Errorf(
"Expected updated remaining balance to be %s, but got %s",
updatedExpectedBalance.ToString(),
updatedActualBalance.ToString(),
)
}
}

func TestGetClaimStatus(t *testing.T) {
airdrop, _ := _setupAirdropConfig(t)

tests := []struct {
name string
claimID uint64
initiallySet bool
expectedStatus bool
}{
{
name: "Unclaimed ID",
claimID: 1,
initiallySet: false,
expectedStatus: false,
},
{
name: "Claimed ID",
claimID: 2,
initiallySet: true,
expectedStatus: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.initiallySet {
word, bit := airdrop.claimIDToBitmapIndex(tt.claimID)
airdrop.claimedBitmap[word] |= 1 << bit
}

jsonStr := airdrop.GetClaimStatus(tt.claimID)

node, err := json.Unmarshal([]byte(jsonStr))
if err != nil {
t.Fatalf("Failed to unmarshal JSON: %v", err)
}

claimID, err := node.MustKey("claim_id").GetString()
if err != nil {
t.Fatalf("Failed to get claim_id: %v", err)
}

if claimID != strconv.FormatUint(tt.claimID, 10) {
t.Errorf("Expected claimID to be %d, but got %v", tt.claimID, claimID)
}

claimed, err := node.MustKey("claimed").GetBool()
if err != nil {
t.Fatalf("Failed to get claimed: %v", err)
}

if claimed != tt.expectedStatus {
t.Errorf("Expected claimed status to be %v, but got %v", tt.expectedStatus, claimed)
}
})
}
}