From 12d7e332a968a838419579784687d21bbd5f1829 Mon Sep 17 00:00:00 2001 From: Lee ByeongJun Date: Tue, 13 Aug 2024 14:26:34 +0900 Subject: [PATCH 1/8] airdrop getters --- airdrop/getter.gno | 115 +++++++++++++++++++++++++ airdrop/getter_test.gno | 180 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 295 insertions(+) create mode 100644 airdrop/getter.gno create mode 100644 airdrop/getter_test.gno diff --git a/airdrop/getter.gno b/airdrop/getter.gno new file mode 100644 index 0000000..3eb366a --- /dev/null +++ b/airdrop/getter.gno @@ -0,0 +1,115 @@ +package airdrop + +import ( + "std" + "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 { + node := json.ObjectNode("", nil) + + err := node.AppendObject("refundable_timestamp", json.StringNode("refundable_timestamp", ufmt.Sprintf("%d", a.config.RefundableTimestamp))) + if err != nil { + panic(err) + } + + err = node.AppendObject("refund_to", json.StringNode("refund_to", a.config.RefundTo.String())) + if err != nil { + panic(err) + } + + b, err := json.Marshal(node) + if err != nil { + panic(err) + } + + return string(b) +} + +// GetTotalClaims returns the total number of claims made so far as a JSON string. +// +// Example: +// { +// "total_claims": "16" +// } +func (a *Airdrop) GetTotalClaims() string { + totalClaims := 0 + for _, bitmap := range a.claimedBitmap { + totalClaims += popCount(bitmap) + } + + node := json.ObjectNode("", nil) + + err := node.AppendObject("total_claims", json.StringNode("total_claims", ufmt.Sprintf("%d", totalClaims))) + if err != nil { + panic(err) + } + + b, err := json.Marshal(node) + if err != nil { + panic(err) + } + + return string(b) +} + +func (a *Airdrop) GetRemainingBalance() string { + balance := a.token.BalanceOf(a.address) + + node := json.ObjectNode("", nil) + + err := node.AppendObject("remaining_balance", json.StringNode("remaining_balance", uint256.NewUint(balance).ToString())) + if err != nil { + panic(err) + } + + b, err := json.Marshal(node) + if err != nil { + panic(err) + } + + return string(b) +} + +func (a *Airdrop) GetClaimStatus(claimID uint64) string { + claimed := a.IsClaimed(claimID) + + node := json.ObjectNode("", nil) + + err := node.AppendObject("claim_id", json.StringNode("claim_id", ufmt.Sprintf("%d", claimID))) + if err != nil { + panic(err) + } + err = node.AppendObject("claimed", json.StringNode("claimed", ufmt.Sprintf("%t", claimed))) + if err != nil { + panic(err) + } + + b, err := json.Marshal(node) + if err != nil { + panic(err) + } + + return string(b) +} + +// 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 +} \ No newline at end of file diff --git a/airdrop/getter_test.gno b/airdrop/getter_test.gno new file mode 100644 index 0000000..8053fc3 --- /dev/null +++ b/airdrop/getter_test.gno @@ -0,0 +1,180 @@ +package airdrop + +import ( + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/json" + u256 "gno.land/p/demo/uint256" +) + +func TestGetAirdropConfig(t *testing.T) { + mockToken := NewMockGRC20("Test Token", "TST", 18) + refundTo := testutils.TestAddress("refund_to") + config := Config{ + RefundableTimestamp: 1234567890, + RefundTo: refundTo, + } + airdrop := NewAirdrop(mockToken, config, refundTo) + + 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) + } +} + +func TestGetTotalClaims(t *testing.T) { + mockToken := NewMockGRC20("Test Token", "TST", 18) + refundTo := testutils.TestAddress("refund_to") + config := Config{ + RefundableTimestamp: 1234567890, + RefundTo: refundTo, + } + ardp := NewAirdrop(mockToken, config, refundTo) + + 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) { + // Create a mock token + mockToken := NewMockGRC20("Test Token", "TST", 18) + + // Create an airdrop instance + 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) { + mockToken := NewMockGRC20("Test Token", "TST", 18) + + airdropAddress := testutils.TestAddress("airdrop_address") + airdrop := NewAirdrop(mockToken, Config{}, airdropAddress) + + // Test unclaimed status + unclaimedID := uint64(1) + jsonStr := airdrop.GetClaimStatus(unclaimedID) + + node, err := json.Unmarshal([]byte(jsonStr)) + if err != nil { + t.Fatalf("Failed to unmarshal JSON: %v", err) + } + + claimID, _ := node.MustKey("claim_id").GetString() + if claimID != "1" { + t.Errorf("Expected claimID to be 1, but got %v", claimID) + } + + claimed, _ := node.MustKey("claimed").GetBool() + if claimed != false { + t.Errorf("Expected claimed status to be false, but got %v", claimed) + } + + // Simulate a claim + claimedID := uint64(2) + word, bit := airdrop.claimIDToBitmapIndex(claimedID) + airdrop.claimedBitmap[word] |= 1 << bit + + jsonStr = airdrop.GetClaimStatus(claimedID) + + updatedNode, err := json.Unmarshal([]byte(jsonStr)) + if err != nil { + t.Fatalf("Failed to unmarshal JSON: %v", err) + } + + claimID2, _ := updatedNode.MustKey("claim_id").GetString() + if claimID2 != "2" { + t.Errorf("Expected claimID to be 2, but got %v", claimID2) + } + + claimed2, _ := updatedNode.MustKey("claimed").GetBool() + if claimed2 != false { + t.Errorf("Expected claimed status to be true, but got %v", claimed2) + } +} From 1000e0434b7894975c282494ee4f1b79c6f85f2e Mon Sep 17 00:00:00 2001 From: Lee ByeongJun Date: Tue, 13 Aug 2024 15:10:20 +0900 Subject: [PATCH 2/8] update GetClaimStatus test and add docs --- airdrop/getter.gno | 86 ++++++++++++++++++++---------------- airdrop/getter_test.gno | 96 +++++++++++++++++++++++------------------ 2 files changed, 102 insertions(+), 80 deletions(-) diff --git a/airdrop/getter.gno b/airdrop/getter.gno index 3eb366a..7f7a249 100644 --- a/airdrop/getter.gno +++ b/airdrop/getter.gno @@ -2,6 +2,7 @@ package airdrop import ( "std" + "strconv" "strings" "gno.land/p/demo/json" @@ -12,14 +13,16 @@ import ( // GetAirdropConfig returns the current configuration of the airdrop as a JSON string. // // Example: -// { -// "refundable_timestamp": "1234567890", -// "refund_to": "g1234567890abcdefghijklmnop" -// } +// +// { +// "refundable_timestamp": "1234567890", +// "refund_to": "g1234567890abcdefghijklmnop" +// } func (a *Airdrop) GetAirdropConfig() string { node := json.ObjectNode("", nil) - err := node.AppendObject("refundable_timestamp", json.StringNode("refundable_timestamp", ufmt.Sprintf("%d", a.config.RefundableTimestamp))) + timestamp := strconv.FormatUint(a.config.RefundableTimestamp, 10) + err := node.AppendObject("refundable_timestamp", json.StringNode("refundable_timestamp", timestamp)) if err != nil { panic(err) } @@ -29,20 +32,16 @@ func (a *Airdrop) GetAirdropConfig() string { panic(err) } - b, err := json.Marshal(node) - if err != nil { - panic(err) - } - - return string(b) + return marshal(node) } // GetTotalClaims returns the total number of claims made so far as a JSON string. // // Example: -// { -// "total_claims": "16" -// } +// +// { +// "total_claims": "16" +// } func (a *Airdrop) GetTotalClaims() string { totalClaims := 0 for _, bitmap := range a.claimedBitmap { @@ -51,57 +50,60 @@ func (a *Airdrop) GetTotalClaims() string { node := json.ObjectNode("", nil) - err := node.AppendObject("total_claims", json.StringNode("total_claims", ufmt.Sprintf("%d", totalClaims))) - if err != nil { - panic(err) - } - - b, err := json.Marshal(node) + claims := strconv.Itoa(totalClaims) + err := node.AppendObject("total_claims", json.StringNode("total_claims", claims)) if err != nil { panic(err) } - return string(b) + return marshal(node) } +// GetRemainingBalance returns the remaining balance of tokens available for airdrop. +// +// Example: +// +// { +// "remaining_balance": "1000000" +// } func (a *Airdrop) GetRemainingBalance() string { balance := a.token.BalanceOf(a.address) - - node := json.ObjectNode("", nil) - err := node.AppendObject("remaining_balance", json.StringNode("remaining_balance", uint256.NewUint(balance).ToString())) - if err != nil { - panic(err) - } + node := json.ObjectNode("", nil) - b, err := json.Marshal(node) + balanceStr := uint256.NewUint(balance).ToString() + err := node.AppendObject("remaining_balance", json.StringNode("remaining_balance", balanceStr)) if err != nil { panic(err) } - return string(b) + 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 { claimed := a.IsClaimed(claimID) node := json.ObjectNode("", nil) - err := node.AppendObject("claim_id", json.StringNode("claim_id", ufmt.Sprintf("%d", claimID))) - if err != nil { - panic(err) - } - err = node.AppendObject("claimed", json.StringNode("claimed", ufmt.Sprintf("%t", claimed))) + id := strconv.FormatUint(claimID, 10) + err := node.AppendObject("claim_id", json.StringNode("claim_id", id)) if err != nil { panic(err) } - - b, err := json.Marshal(node) + err = node.AppendObject("claimed", json.BoolNode("claimed", claimed)) if err != nil { panic(err) } - return string(b) + return marshal(node) } // popCount returns the number of set bits in a uint64 @@ -112,4 +114,12 @@ func popCount(x uint64) int { x &= x - 1 } return count -} \ No newline at end of file +} + +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) +} diff --git a/airdrop/getter_test.gno b/airdrop/getter_test.gno index 8053fc3..c0f5148 100644 --- a/airdrop/getter_test.gno +++ b/airdrop/getter_test.gno @@ -1,10 +1,11 @@ package airdrop import ( + "strconv" "testing" - "gno.land/p/demo/testutils" "gno.land/p/demo/json" + "gno.land/p/demo/testutils" u256 "gno.land/p/demo/uint256" ) @@ -62,10 +63,8 @@ func TestGetTotalClaims(t *testing.T) { } func TestGetRemainingBalance(t *testing.T) { - // Create a mock token mockToken := NewMockGRC20("Test Token", "TST", 18) - // Create an airdrop instance airdropAddress := testutils.TestAddress("airdrop_address") airdrop := NewAirdrop(mockToken, Config{}, airdropAddress) @@ -137,44 +136,57 @@ func TestGetClaimStatus(t *testing.T) { airdropAddress := testutils.TestAddress("airdrop_address") airdrop := NewAirdrop(mockToken, Config{}, airdropAddress) - // Test unclaimed status - unclaimedID := uint64(1) - jsonStr := airdrop.GetClaimStatus(unclaimedID) - - node, err := json.Unmarshal([]byte(jsonStr)) - if err != nil { - t.Fatalf("Failed to unmarshal JSON: %v", err) - } - - claimID, _ := node.MustKey("claim_id").GetString() - if claimID != "1" { - t.Errorf("Expected claimID to be 1, but got %v", claimID) - } - - claimed, _ := node.MustKey("claimed").GetBool() - if claimed != false { - t.Errorf("Expected claimed status to be false, but got %v", claimed) - } - - // Simulate a claim - claimedID := uint64(2) - word, bit := airdrop.claimIDToBitmapIndex(claimedID) - airdrop.claimedBitmap[word] |= 1 << bit - - jsonStr = airdrop.GetClaimStatus(claimedID) - - updatedNode, err := json.Unmarshal([]byte(jsonStr)) - if err != nil { - t.Fatalf("Failed to unmarshal JSON: %v", err) - } - - claimID2, _ := updatedNode.MustKey("claim_id").GetString() - if claimID2 != "2" { - t.Errorf("Expected claimID to be 2, but got %v", claimID2) - } - - claimed2, _ := updatedNode.MustKey("claimed").GetBool() - if claimed2 != false { - t.Errorf("Expected claimed status to be true, but got %v", claimed2) + 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) + } + }) } } From 1af3ab178efcb05c456c0a7244b6a10b0c814dad Mon Sep 17 00:00:00 2001 From: Lee ByeongJun Date: Tue, 13 Aug 2024 16:12:31 +0900 Subject: [PATCH 3/8] update test --- airdrop/getter_test.gno | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/airdrop/getter_test.gno b/airdrop/getter_test.gno index c0f5148..6b2cc34 100644 --- a/airdrop/getter_test.gno +++ b/airdrop/getter_test.gno @@ -1,6 +1,7 @@ package airdrop import ( + "std" "strconv" "testing" @@ -9,14 +10,20 @@ import ( u256 "gno.land/p/demo/uint256" ) -func TestGetAirdropConfig(t *testing.T) { +func _setupAirdropConfig(t *testing.T) (*Airdrop, std.Address) { mockToken := NewMockGRC20("Test Token", "TST", 18) - refundTo := testutils.TestAddress("refund_to") + addr := testutils.TestAddress("addr") config := Config{ RefundableTimestamp: 1234567890, - RefundTo: refundTo, + RefundTo: addr, } - airdrop := NewAirdrop(mockToken, config, refundTo) + airdrop := NewAirdrop(mockToken, config, addr) + + return airdrop, addr +} + +func TestGetAirdropConfig(t *testing.T) { + airdrop, refundTo := _setupAirdropConfig(t) jsonStr := airdrop.GetAirdropConfig() @@ -34,16 +41,14 @@ func TestGetAirdropConfig(t *testing.T) { 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) { - mockToken := NewMockGRC20("Test Token", "TST", 18) - refundTo := testutils.TestAddress("refund_to") - config := Config{ - RefundableTimestamp: 1234567890, - RefundTo: refundTo, - } - ardp := NewAirdrop(mockToken, config, refundTo) + ardp, _ := _setupAirdropConfig(t) ardp.claimedBitmap[0] = 0b10101010 ardp.claimedBitmap[1] = 0b01010101 @@ -97,8 +102,6 @@ func TestGetRemainingBalance(t *testing.T) { ) } - ///////////////////////////////////////////////////////////////////////////////// - // Simulate some claims claimedAmount := uint64(300000) mockToken.balances[airdropAddress] -= claimedAmount @@ -131,10 +134,7 @@ func TestGetRemainingBalance(t *testing.T) { } func TestGetClaimStatus(t *testing.T) { - mockToken := NewMockGRC20("Test Token", "TST", 18) - - airdropAddress := testutils.TestAddress("airdrop_address") - airdrop := NewAirdrop(mockToken, Config{}, airdropAddress) + airdrop, _ := _setupAirdropConfig(t) tests := []struct { name string From a24003903330ffb7ba3e1f7b0180a6e6e8da951e Mon Sep 17 00:00:00 2001 From: Lee ByeongJun Date: Tue, 13 Aug 2024 16:36:18 +0900 Subject: [PATCH 4/8] remove pointer receivers --- airdrop/getter.gno | 8 ++++---- airdrop/getter_test.gno | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/airdrop/getter.gno b/airdrop/getter.gno index 7f7a249..eab000a 100644 --- a/airdrop/getter.gno +++ b/airdrop/getter.gno @@ -18,7 +18,7 @@ import ( // "refundable_timestamp": "1234567890", // "refund_to": "g1234567890abcdefghijklmnop" // } -func (a *Airdrop) GetAirdropConfig() string { +func GetAirdropConfig(a *Airdrop) string { node := json.ObjectNode("", nil) timestamp := strconv.FormatUint(a.config.RefundableTimestamp, 10) @@ -42,7 +42,7 @@ func (a *Airdrop) GetAirdropConfig() string { // { // "total_claims": "16" // } -func (a *Airdrop) GetTotalClaims() string { +func GetTotalClaims(a *Airdrop) string { totalClaims := 0 for _, bitmap := range a.claimedBitmap { totalClaims += popCount(bitmap) @@ -66,7 +66,7 @@ func (a *Airdrop) GetTotalClaims() string { // { // "remaining_balance": "1000000" // } -func (a *Airdrop) GetRemainingBalance() string { +func GetRemainingBalance(a *Airdrop) string { balance := a.token.BalanceOf(a.address) node := json.ObjectNode("", nil) @@ -88,7 +88,7 @@ func (a *Airdrop) GetRemainingBalance() string { // "claim_id": "123", // "claimed": true // } -func (a *Airdrop) GetClaimStatus(claimID uint64) string { +func GetClaimStatus(a *Airdrop, claimID uint64) string { claimed := a.IsClaimed(claimID) node := json.ObjectNode("", nil) diff --git a/airdrop/getter_test.gno b/airdrop/getter_test.gno index 6b2cc34..b2c8d7b 100644 --- a/airdrop/getter_test.gno +++ b/airdrop/getter_test.gno @@ -25,7 +25,7 @@ func _setupAirdropConfig(t *testing.T) (*Airdrop, std.Address) { func TestGetAirdropConfig(t *testing.T) { airdrop, refundTo := _setupAirdropConfig(t) - jsonStr := airdrop.GetAirdropConfig() + jsonStr := GetAirdropConfig(airdrop) node, err := json.Unmarshal([]byte(jsonStr)) if err != nil { @@ -54,7 +54,7 @@ func TestGetTotalClaims(t *testing.T) { ardp.claimedBitmap[1] = 0b01010101 ardp.claimedBitmap[2] = 0b11111111 - jsonStr := ardp.GetTotalClaims() + jsonStr := GetTotalClaims(ardp) node, err := json.Unmarshal([]byte(jsonStr)) if err != nil { @@ -77,7 +77,7 @@ func TestGetRemainingBalance(t *testing.T) { initialBalance := uint64(1000000) mockToken.balances[airdropAddress] = initialBalance - jsonStr := airdrop.GetRemainingBalance() + jsonStr := GetRemainingBalance(airdrop) node, err := json.Unmarshal([]byte(jsonStr)) if err != nil { t.Fatalf("Failed to unmarshal JSON: %v", err) @@ -106,7 +106,7 @@ func TestGetRemainingBalance(t *testing.T) { claimedAmount := uint64(300000) mockToken.balances[airdropAddress] -= claimedAmount - jsonStr = airdrop.GetRemainingBalance() + jsonStr = GetRemainingBalance(airdrop) updatedNode, err := json.Unmarshal([]byte(jsonStr)) if err != nil { @@ -163,7 +163,7 @@ func TestGetClaimStatus(t *testing.T) { airdrop.claimedBitmap[word] |= 1 << bit } - jsonStr := airdrop.GetClaimStatus(tt.claimID) + jsonStr := GetClaimStatus(airdrop, tt.claimID) node, err := json.Unmarshal([]byte(jsonStr)) if err != nil { From 5191c5a64310afca1c74fc46e0f2601fbf95ce1e Mon Sep 17 00:00:00 2001 From: Lee ByeongJun Date: Wed, 14 Aug 2024 10:51:45 +0900 Subject: [PATCH 5/8] build airdrop from json string --- airdrop/airdrop.gno | 3 + airdrop/getter.gno | 90 +++++++++++++++++++++ airdrop/getter_test.gno | 74 +++++++++++++++++ airdrop/utils.gno | 175 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 342 insertions(+) create mode 100644 airdrop/utils.gno diff --git a/airdrop/airdrop.gno b/airdrop/airdrop.gno index 8a95f29..588ca0a 100644 --- a/airdrop/airdrop.gno +++ b/airdrop/airdrop.gno @@ -44,8 +44,11 @@ type Airdrop struct { snapshotManager *snapshot.Manager } +var airdropToken grc20.Token + // NewAirdrop creates a new Airdrop instance func NewAirdrop(token grc20.Token, config Config, addr std.Address) *Airdrop { + airdropToken = token return &Airdrop{ token: token, config: config, diff --git a/airdrop/getter.gno b/airdrop/getter.gno index eab000a..b64e74e 100644 --- a/airdrop/getter.gno +++ b/airdrop/getter.gno @@ -5,6 +5,7 @@ import ( "strconv" "strings" + "gno.land/p/demo/grc/grc20" "gno.land/p/demo/json" "gno.land/p/demo/ufmt" "gno.land/p/demo/uint256" @@ -123,3 +124,92 @@ func marshal(node *json.Node) string { } return string(b) } + +type AirdropData struct { + Config Config `json:"config"` + Address std.Address `json:"address"` + ClaimedBitmap map[uint64]uint64 `json:"claimed_bitmap"` +} + +func airdropToJSON(a *Airdrop) string { + node := json.ObjectNode("", nil) + + configNode := json.ObjectNode("config", nil) + err := configNode.AppendObject("refundable_timestamp", json.StringNode("refundable_timestamp", strconv.FormatUint(a.config.RefundableTimestamp, 10))) + if err != nil { + panic(err) + } + err = configNode.AppendObject("refund_to", json.StringNode("refund_to", a.config.RefundTo.String())) + if err != nil { + panic(err) + } + + err = node.AppendObject("config", configNode) + if err != nil { + panic(err) + } + + err = node.AppendObject("address", json.StringNode("address", a.address.String())) + if err != nil { + panic(err) + } + + claimedBitmapNode := json.ObjectNode("claimed_bitmap", nil) + for i, bitmap := range a.claimedBitmap { + err = claimedBitmapNode.AppendObject(strconv.FormatUint(i, 10), json.StringNode(strconv.FormatUint(i, 10), strconv.FormatUint(bitmap, 2))) + if err != nil { + panic(err) + } + } + + err = node.AppendObject("claimed_bitmap", claimedBitmapNode) + if err != nil { + panic(err) + } + + return marshal(node) +} + +func createAirdropFromJSON(jsonStr string) *Airdrop { + node, err := json.Unmarshal([]byte(jsonStr)) + if err != nil { + panic(ufmt.Sprintf("failed to unmarshal JSON: %v", err)) + } + + configNode := node.MustKey("config") + + timestamp := configNode.MustKey("refundable_timestamp").MustString() + refundableTimestamp, err := parseUint(timestamp, 10, 64) + if err != nil { + panic(err) + } + + refundToAddr := configNode.MustKey("refund_to").MustString() + refundTo := std.Address(refundToAddr) + + address := std.Address(node.MustKey("address").MustString()) + + claimedBitmapNode := node.MustKey("claimed_bitmap") + claimedBitmap := make(map[uint64]uint64) + + claimedBitmapNode.ObjectEach(func(key string, value *json.Node) { + index, err := parseUint(key, 10, 64) + if err != nil { + panic(err) + } + bitmap, err := parseUint(value.MustString(), 2, 64) + if err != nil { + panic(err) + } + claimedBitmap[index] = bitmap + }) + + return &Airdrop{ + config: Config{ + RefundableTimestamp: refundableTimestamp, + RefundTo: refundTo, + }, + address: address, + claimedBitmap: claimedBitmap, + } +} \ No newline at end of file diff --git a/airdrop/getter_test.gno b/airdrop/getter_test.gno index b2c8d7b..fb91a7f 100644 --- a/airdrop/getter_test.gno +++ b/airdrop/getter_test.gno @@ -190,3 +190,77 @@ func TestGetClaimStatus(t *testing.T) { }) } } + +func TestAirdropToJSONAndCreateAirdropFromJSON(t *testing.T) { + mockToken := NewMockGRC20("Test Token", "TST", 18) + airdropAddress := testutils.TestAddress("airdrop_address") + refundTo := testutils.TestAddress("refund_to") + config := Config{ + RefundableTimestamp: 1234567890, + RefundTo: refundTo, + } + airdrop := NewAirdrop(mockToken, config, airdropAddress) + + // Set some claimed bits + airdrop.claimedBitmap[0] = 0b101 + airdrop.claimedBitmap[1] = 0b11010 + + jsonStr := airdropToJSON(airdrop) + newAirdrop := createAirdropFromJSON(jsonStr) + + if newAirdrop.config.RefundableTimestamp != airdrop.config.RefundableTimestamp { + t.Errorf("RefundableTimestamp mismatch. Expected %d, got %d", + airdrop.config.RefundableTimestamp, newAirdrop.config.RefundableTimestamp) + } + + if newAirdrop.config.RefundTo != airdrop.config.RefundTo { + t.Errorf("RefundTo mismatch. Expected %s, got %s", + airdrop.config.RefundTo, newAirdrop.config.RefundTo) + } + + if newAirdrop.address != airdrop.address { + t.Errorf("Address mismatch. Expected %s, got %s", + airdrop.address, newAirdrop.address) + } + + if len(newAirdrop.claimedBitmap) != len(airdrop.claimedBitmap) { + t.Errorf("ClaimedBitmap length mismatch. Expected %d, got %d", + len(airdrop.claimedBitmap), len(newAirdrop.claimedBitmap)) + } + + for k, v := range airdrop.claimedBitmap { + if newAirdrop.claimedBitmap[k] != v { + t.Errorf("ClaimedBitmap mismatch for key %d. Expected %d, got %d", + k, v, newAirdrop.claimedBitmap[k]) + } + } + + root, err := json.Unmarshal([]byte(jsonStr)) + if err != nil { + t.Fatalf("Failed to unmarshal JSON: %v", err) + } + + configNode := root.MustKey("config") + + if configNode.MustKey("refundable_timestamp").MustString() != "1234567890" { + t.Errorf("RefundableTimestamp mismatch in JSON. Expected 1234567890, got %s", + configNode.MustKey("refundable_timestamp").MustString()) + } + + if configNode.MustKey("refund_to").MustString() != refundTo.String() { + t.Errorf("RefundTo mismatch in JSON. Expected %s, got %s", + refundTo.String(), configNode.MustKey("refund_to").MustString()) + } + + claimedBitmapNode := root.MustKey("claimed_bitmap") + + if claimedBitmapNode.MustKey("0").MustString() != "101" { + t.Errorf("ClaimedBitmap[0] mismatch in JSON. Expected 5 (101), got %s", + claimedBitmapNode.MustKey("0").MustString()) + } + + if claimedBitmapNode.MustKey("1").MustString() != "11010" { + t.Errorf("ClaimedBitmap[1] mismatch in JSON. Expected 26 (11010), got %s", + claimedBitmapNode.MustKey("1").MustString()) + } +} diff --git a/airdrop/utils.gno b/airdrop/utils.gno new file mode 100644 index 0000000..606dfdc --- /dev/null +++ b/airdrop/utils.gno @@ -0,0 +1,175 @@ +package airdrop + +import ( + "gno.land/p/demo/ufmt" +) + +const ( + MaxUint64 = 1<<64 - 1 + uintSize = 32 << (^uint(0) >> 63) +) + +func lower(c byte) byte { + return c | ('x' - 'X') +} + +// TODO: Remove parseUint after gno supports strconv.ParseUint +func parseUint(s string, base int, bitSize int) (uint64, error) { + const fnParseUint = "ParseUint" + + if s == "" { + return 0, ufmt.Errorf("%s: parsing \"\": invalid syntax", fnParseUint) + } + + base0 := base == 0 + + s0 := s + switch { + case 2 <= base && base <= 36: + // valid base; nothing to do + + case base == 0: + // Look for octal, hex prefix. + base = 10 + if s[0] == '0' { + switch { + case len(s) >= 3 && lower(s[1]) == 'b': + base = 2 + s = s[2:] + case len(s) >= 3 && lower(s[1]) == 'o': + base = 8 + s = s[2:] + case len(s) >= 3 && lower(s[1]) == 'x': + base = 16 + s = s[2:] + default: + base = 8 + s = s[1:] + } + } + + default: + return 0, ufmt.Errorf("%s: invalid base %d", fnParseUint, base) + } + + if bitSize == 0 { + bitSize = uintSize + } else if bitSize < 0 || bitSize > 64 { + return 0, ufmt.Errorf("%s: invalid bit size %d", fnParseUint, bitSize) + } + + // Cutoff is the smallest number such that cutoff*base > maxUint64. + // Use compile-time constants for common cases. + var cutoff uint64 + switch base { + case 10: + cutoff = MaxUint64/10 + 1 + case 16: + cutoff = MaxUint64/16 + 1 + default: + cutoff = MaxUint64/uint64(base) + 1 + } + + maxVal := uint64(1)<= byte(base) { + return 0, ufmt.Errorf("%s: invalid character", fnParseUint) + } + + if n >= cutoff { + // n*base overflows + return maxVal, ufmt.Errorf("%s: value out of range", fnParseUint) + } + n *= uint64(base) + + n1 := n + uint64(d) + if n1 < n || n1 > maxVal { + // n+d overflows + return maxVal, ufmt.Errorf("%s: value out of range", fnParseUint) + } + n = n1 + } + + if underscores && !underscoreOK(s0) { + return 0, ufmt.Errorf("%s: invalid underscore", fnParseUint) + } + + return n, nil +} + +func underscoreOK(s string) bool { + // saw tracks the last character (class) we saw: + // ^ for beginning of number, + // 0 for a digit or base prefix, + // _ for an underscore, + // ! for none of the above. + saw := '^' + i := 0 + + // Optional sign. + if len(s) >= 1 && (s[0] == '-' || s[0] == '+') { + s = s[1:] + } + + // Optional base prefix. + hex := false + if len(s) >= 2 && s[0] == '0' && (lower(s[1]) == 'b' || lower(s[1]) == 'o' || lower(s[1]) == 'x') { + i = 2 + saw = '0' // base prefix counts as a digit for "underscore as digit separator" + hex = lower(s[1]) == 'x' + } + + // Number proper. + for ; i < len(s); i++ { + // Digits are always okay. + if '0' <= s[i] && s[i] <= '9' || hex && 'a' <= lower(s[i]) && lower(s[i]) <= 'f' { + saw = '0' + continue + } + // Underscore must follow digit. + if s[i] == '_' { + if saw != '0' { + return false + } + saw = '_' + continue + } + // Underscore must also be followed by digit. + if saw == '_' { + return false + } + // Saw non-digit, non-underscore. + saw = '!' + } + return saw != '_' +} + +func b2i(b bool) uint8 { + if b { + return 1 + } + return 0 +} + +func b2s(b bool) string { + if b { + return "true" + } + return "false" +} From b1335b03623b74940851e9db25146e9218be526d Mon Sep 17 00:00:00 2001 From: Lee ByeongJun Date: Wed, 14 Aug 2024 11:19:04 +0900 Subject: [PATCH 6/8] pure param getters --- airdrop/getter.gno | 81 ++++++++++--------- airdrop/getter_test.gno | 171 +++++++++++++++++++++------------------- 2 files changed, 133 insertions(+), 119 deletions(-) diff --git a/airdrop/getter.gno b/airdrop/getter.gno index b64e74e..137a6d9 100644 --- a/airdrop/getter.gno +++ b/airdrop/getter.gno @@ -19,16 +19,18 @@ import ( // "refundable_timestamp": "1234567890", // "refund_to": "g1234567890abcdefghijklmnop" // } -func GetAirdropConfig(a *Airdrop) string { +func GetAirdropConfig(jsonStr string) string { + airdrop := createAirdropFromJSON(jsonStr) + node := json.ObjectNode("", nil) - timestamp := strconv.FormatUint(a.config.RefundableTimestamp, 10) + timestamp := strconv.FormatUint(airdrop.config.RefundableTimestamp, 10) err := node.AppendObject("refundable_timestamp", json.StringNode("refundable_timestamp", timestamp)) if err != nil { panic(err) } - err = node.AppendObject("refund_to", json.StringNode("refund_to", a.config.RefundTo.String())) + err = node.AppendObject("refund_to", json.StringNode("refund_to", airdrop.config.RefundTo.String())) if err != nil { panic(err) } @@ -43,9 +45,11 @@ func GetAirdropConfig(a *Airdrop) string { // { // "total_claims": "16" // } -func GetTotalClaims(a *Airdrop) string { +func GetTotalClaims(jsonStr string) string { + airdrop := createAirdropFromJSON(jsonStr) + totalClaims := 0 - for _, bitmap := range a.claimedBitmap { + for _, bitmap := range airdrop.claimedBitmap { totalClaims += popCount(bitmap) } @@ -67,11 +71,14 @@ func GetTotalClaims(a *Airdrop) string { // { // "remaining_balance": "1000000" // } -func GetRemainingBalance(a *Airdrop) string { - balance := a.token.BalanceOf(a.address) +// +// GetRemainingBalance returns the remaining balance of tokens available for airdrop. +func GetRemainingBalance(jsonStr string) string { + airdrop := createAirdropFromJSON(jsonStr) - node := json.ObjectNode("", nil) + balance := airdropToken.BalanceOf(airdrop.address) + node := json.ObjectNode("", nil) balanceStr := uint256.NewUint(balance).ToString() err := node.AppendObject("remaining_balance", json.StringNode("remaining_balance", balanceStr)) if err != nil { @@ -89,8 +96,10 @@ func GetRemainingBalance(a *Airdrop) string { // "claim_id": "123", // "claimed": true // } -func GetClaimStatus(a *Airdrop, claimID uint64) string { - claimed := a.IsClaimed(claimID) +func GetClaimStatus(jsonStr string, claimID uint64) string { + airdrop := createAirdropFromJSON(jsonStr) + + claimed := airdrop.IsClaimed(claimID) node := json.ObjectNode("", nil) @@ -107,31 +116,8 @@ func GetClaimStatus(a *Airdrop, claimID uint64) string { 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) -} - -type AirdropData struct { - Config Config `json:"config"` - Address std.Address `json:"address"` - ClaimedBitmap map[uint64]uint64 `json:"claimed_bitmap"` -} - -func airdropToJSON(a *Airdrop) string { +// AirdropToJSON converts an Airdrop struct to a JSON string. +func AirdropToJSON(a *Airdrop) string { node := json.ObjectNode("", nil) configNode := json.ObjectNode("config", nil) @@ -170,6 +156,7 @@ func airdropToJSON(a *Airdrop) string { return marshal(node) } +// createAirdropFromJSON creates an Airdrop struct from a JSON string. func createAirdropFromJSON(jsonStr string) *Airdrop { node, err := json.Unmarshal([]byte(jsonStr)) if err != nil { @@ -207,9 +194,27 @@ func createAirdropFromJSON(jsonStr string) *Airdrop { return &Airdrop{ config: Config{ RefundableTimestamp: refundableTimestamp, - RefundTo: refundTo, + RefundTo: refundTo, }, - address: address, + address: address, claimedBitmap: claimedBitmap, } -} \ No newline at end of file +} + +// 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) +} diff --git a/airdrop/getter_test.gno b/airdrop/getter_test.gno index fb91a7f..f87c6a0 100644 --- a/airdrop/getter_test.gno +++ b/airdrop/getter_test.gno @@ -25,9 +25,10 @@ func _setupAirdropConfig(t *testing.T) (*Airdrop, std.Address) { func TestGetAirdropConfig(t *testing.T) { airdrop, refundTo := _setupAirdropConfig(t) - jsonStr := GetAirdropConfig(airdrop) + jsonStr := AirdropToJSON(airdrop) + result := GetAirdropConfig(jsonStr) - node, err := json.Unmarshal([]byte(jsonStr)) + node, err := json.Unmarshal([]byte(result)) if err != nil { t.Fatalf("Failed to unmarshal JSON: %v", err) } @@ -54,9 +55,11 @@ func TestGetTotalClaims(t *testing.T) { ardp.claimedBitmap[1] = 0b01010101 ardp.claimedBitmap[2] = 0b11111111 - jsonStr := GetTotalClaims(ardp) + jsonStr := AirdropToJSON(ardp) - node, err := json.Unmarshal([]byte(jsonStr)) + result := GetTotalClaims(jsonStr) + + node, err := json.Unmarshal([]byte(result)) if err != nil { t.Fatalf("Failed to unmarshal JSON: %v", err) } @@ -77,13 +80,15 @@ func TestGetRemainingBalance(t *testing.T) { initialBalance := uint64(1000000) mockToken.balances[airdropAddress] = initialBalance - jsonStr := GetRemainingBalance(airdrop) + airdropJSON := AirdropToJSON(airdrop) + + jsonStr := GetRemainingBalance(airdropJSON) node, err := json.Unmarshal([]byte(jsonStr)) if err != nil { t.Fatalf("Failed to unmarshal JSON: %v", err) } - remainingBalance, _ := node.MustKey("remaining_balance").GetString() + remainingBalance := node.MustKey("remaining_balance").MustString() if remainingBalance != "1000000" { t.Errorf("Expected remaining balance to be 1000000, but got %v", remainingBalance) } @@ -106,14 +111,17 @@ func TestGetRemainingBalance(t *testing.T) { claimedAmount := uint64(300000) mockToken.balances[airdropAddress] -= claimedAmount - jsonStr = GetRemainingBalance(airdrop) + // Update airdrop JSON + airdropJSON = AirdropToJSON(airdrop) + + jsonStr = GetRemainingBalance(airdropJSON) updatedNode, err := json.Unmarshal([]byte(jsonStr)) if err != nil { t.Fatalf("Failed to unmarshal updated JSON: %v", err) } - updatedRemainingBalance, _ := updatedNode.MustKey("remaining_balance").GetString() + updatedRemainingBalance := updatedNode.MustKey("remaining_balance").MustString() if updatedRemainingBalance != "700000" { t.Errorf("Expected updated remaining balance to be 700000, but got %v", updatedRemainingBalance) } @@ -163,9 +171,10 @@ func TestGetClaimStatus(t *testing.T) { airdrop.claimedBitmap[word] |= 1 << bit } - jsonStr := GetClaimStatus(airdrop, tt.claimID) + jsonStr := AirdropToJSON(airdrop) + res := GetClaimStatus(jsonStr, tt.claimID) - node, err := json.Unmarshal([]byte(jsonStr)) + node, err := json.Unmarshal([]byte(res)) if err != nil { t.Fatalf("Failed to unmarshal JSON: %v", err) } @@ -192,75 +201,75 @@ func TestGetClaimStatus(t *testing.T) { } func TestAirdropToJSONAndCreateAirdropFromJSON(t *testing.T) { - mockToken := NewMockGRC20("Test Token", "TST", 18) - airdropAddress := testutils.TestAddress("airdrop_address") - refundTo := testutils.TestAddress("refund_to") - config := Config{ - RefundableTimestamp: 1234567890, - RefundTo: refundTo, - } - airdrop := NewAirdrop(mockToken, config, airdropAddress) - - // Set some claimed bits - airdrop.claimedBitmap[0] = 0b101 - airdrop.claimedBitmap[1] = 0b11010 - - jsonStr := airdropToJSON(airdrop) - newAirdrop := createAirdropFromJSON(jsonStr) - - if newAirdrop.config.RefundableTimestamp != airdrop.config.RefundableTimestamp { - t.Errorf("RefundableTimestamp mismatch. Expected %d, got %d", - airdrop.config.RefundableTimestamp, newAirdrop.config.RefundableTimestamp) - } - - if newAirdrop.config.RefundTo != airdrop.config.RefundTo { - t.Errorf("RefundTo mismatch. Expected %s, got %s", - airdrop.config.RefundTo, newAirdrop.config.RefundTo) - } - - if newAirdrop.address != airdrop.address { - t.Errorf("Address mismatch. Expected %s, got %s", - airdrop.address, newAirdrop.address) - } - - if len(newAirdrop.claimedBitmap) != len(airdrop.claimedBitmap) { - t.Errorf("ClaimedBitmap length mismatch. Expected %d, got %d", - len(airdrop.claimedBitmap), len(newAirdrop.claimedBitmap)) - } - - for k, v := range airdrop.claimedBitmap { - if newAirdrop.claimedBitmap[k] != v { - t.Errorf("ClaimedBitmap mismatch for key %d. Expected %d, got %d", - k, v, newAirdrop.claimedBitmap[k]) - } - } - - root, err := json.Unmarshal([]byte(jsonStr)) - if err != nil { - t.Fatalf("Failed to unmarshal JSON: %v", err) - } - - configNode := root.MustKey("config") - - if configNode.MustKey("refundable_timestamp").MustString() != "1234567890" { - t.Errorf("RefundableTimestamp mismatch in JSON. Expected 1234567890, got %s", - configNode.MustKey("refundable_timestamp").MustString()) - } - - if configNode.MustKey("refund_to").MustString() != refundTo.String() { - t.Errorf("RefundTo mismatch in JSON. Expected %s, got %s", - refundTo.String(), configNode.MustKey("refund_to").MustString()) - } - - claimedBitmapNode := root.MustKey("claimed_bitmap") - - if claimedBitmapNode.MustKey("0").MustString() != "101" { - t.Errorf("ClaimedBitmap[0] mismatch in JSON. Expected 5 (101), got %s", - claimedBitmapNode.MustKey("0").MustString()) - } - - if claimedBitmapNode.MustKey("1").MustString() != "11010" { - t.Errorf("ClaimedBitmap[1] mismatch in JSON. Expected 26 (11010), got %s", - claimedBitmapNode.MustKey("1").MustString()) - } + mockToken := NewMockGRC20("Test Token", "TST", 18) + airdropAddress := testutils.TestAddress("airdrop_address") + refundTo := testutils.TestAddress("refund_to") + config := Config{ + RefundableTimestamp: 1234567890, + RefundTo: refundTo, + } + airdrop := NewAirdrop(mockToken, config, airdropAddress) + + // Set some claimed bits + airdrop.claimedBitmap[0] = 0b101 + airdrop.claimedBitmap[1] = 0b11010 + + jsonStr := AirdropToJSON(airdrop) + newAirdrop := createAirdropFromJSON(jsonStr) + + if newAirdrop.config.RefundableTimestamp != airdrop.config.RefundableTimestamp { + t.Errorf("RefundableTimestamp mismatch. Expected %d, got %d", + airdrop.config.RefundableTimestamp, newAirdrop.config.RefundableTimestamp) + } + + if newAirdrop.config.RefundTo != airdrop.config.RefundTo { + t.Errorf("RefundTo mismatch. Expected %s, got %s", + airdrop.config.RefundTo, newAirdrop.config.RefundTo) + } + + if newAirdrop.address != airdrop.address { + t.Errorf("Address mismatch. Expected %s, got %s", + airdrop.address, newAirdrop.address) + } + + if len(newAirdrop.claimedBitmap) != len(airdrop.claimedBitmap) { + t.Errorf("ClaimedBitmap length mismatch. Expected %d, got %d", + len(airdrop.claimedBitmap), len(newAirdrop.claimedBitmap)) + } + + for k, v := range airdrop.claimedBitmap { + if newAirdrop.claimedBitmap[k] != v { + t.Errorf("ClaimedBitmap mismatch for key %d. Expected %d, got %d", + k, v, newAirdrop.claimedBitmap[k]) + } + } + + root, err := json.Unmarshal([]byte(jsonStr)) + if err != nil { + t.Fatalf("Failed to unmarshal JSON: %v", err) + } + + configNode := root.MustKey("config") + + if configNode.MustKey("refundable_timestamp").MustString() != "1234567890" { + t.Errorf("RefundableTimestamp mismatch in JSON. Expected 1234567890, got %s", + configNode.MustKey("refundable_timestamp").MustString()) + } + + if configNode.MustKey("refund_to").MustString() != refundTo.String() { + t.Errorf("RefundTo mismatch in JSON. Expected %s, got %s", + refundTo.String(), configNode.MustKey("refund_to").MustString()) + } + + claimedBitmapNode := root.MustKey("claimed_bitmap") + + if claimedBitmapNode.MustKey("0").MustString() != "101" { + t.Errorf("ClaimedBitmap[0] mismatch in JSON. Expected 5 (101), got %s", + claimedBitmapNode.MustKey("0").MustString()) + } + + if claimedBitmapNode.MustKey("1").MustString() != "11010" { + t.Errorf("ClaimedBitmap[1] mismatch in JSON. Expected 26 (11010), got %s", + claimedBitmapNode.MustKey("1").MustString()) + } } From 0b44c70f44113b96b3849a6a8bca43614a1c4be3 Mon Sep 17 00:00:00 2001 From: Lee ByeongJun Date: Wed, 21 Aug 2024 11:21:56 +0900 Subject: [PATCH 7/8] strconv --- airdrop/getter.gno | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/airdrop/getter.gno b/airdrop/getter.gno index 137a6d9..069db8e 100644 --- a/airdrop/getter.gno +++ b/airdrop/getter.gno @@ -166,7 +166,7 @@ func createAirdropFromJSON(jsonStr string) *Airdrop { configNode := node.MustKey("config") timestamp := configNode.MustKey("refundable_timestamp").MustString() - refundableTimestamp, err := parseUint(timestamp, 10, 64) + refundableTimestamp, err := strconv.Atoi(timestamp) if err != nil { panic(err) } @@ -180,7 +180,7 @@ func createAirdropFromJSON(jsonStr string) *Airdrop { claimedBitmap := make(map[uint64]uint64) claimedBitmapNode.ObjectEach(func(key string, value *json.Node) { - index, err := parseUint(key, 10, 64) + index, err := strconv.Atoi(key) if err != nil { panic(err) } @@ -188,12 +188,12 @@ func createAirdropFromJSON(jsonStr string) *Airdrop { if err != nil { panic(err) } - claimedBitmap[index] = bitmap + claimedBitmap[uint64(index)] = bitmap }) return &Airdrop{ config: Config{ - RefundableTimestamp: refundableTimestamp, + RefundableTimestamp: uint64(refundableTimestamp), RefundTo: refundTo, }, address: address, From faca1f9a87b67e165a1e9b404238e201083fc1c9 Mon Sep 17 00:00:00 2001 From: Lee ByeongJun Date: Wed, 21 Aug 2024 11:27:13 +0900 Subject: [PATCH 8/8] json example --- airdrop/getter.gno | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/airdrop/getter.gno b/airdrop/getter.gno index 069db8e..f4c2491 100644 --- a/airdrop/getter.gno +++ b/airdrop/getter.gno @@ -117,6 +117,20 @@ func GetClaimStatus(jsonStr string, claimID uint64) string { } // AirdropToJSON converts an Airdrop struct to a JSON string. +// +// Example: +// +// { +// "config": { +// "refundable_timestamp": "1234567890", +// "refund_to": "g1234567890abcdefghijklmnop" +// }, +// "address": "g1234567890abcdefghijklmnop", +// "claimed_bitmap": { // ref: getter_test.gno/TestAirdropToJSONAndCreateAirdropFromJSON +// "0": "101", +// "1": "1101", +// }, +// } func AirdropToJSON(a *Airdrop) string { node := json.ObjectNode("", nil)