Skip to content

Commit

Permalink
Update merkletree, fetching snapshot, max claimable (thirdweb-dev#91)
Browse files Browse the repository at this point in the history
* Update merkletree, fetching snapshot, max claimable

* Update merkle tree

* Update ineligibility reasons

* Update

* Propagate error
  • Loading branch information
adam-maj authored Nov 18, 2022
1 parent 6c14a6b commit 88d7951
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 54 deletions.
4 changes: 2 additions & 2 deletions cmd/thirdweb/edition_drop_commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ var editionDropGetActiveCmd = &cobra.Command{
fmt.Printf(fmt.Sprintf("\n\nClaim Condition %d\n================\n", i))
fmt.Println("Start Time:", c.StartTime)
fmt.Println("Available:", c.AvailableSupply)
fmt.Println("Quantity:", c.MaxQuantity)
fmt.Println("Quantity:", c.MaxClaimableSupply)
fmt.Println("Quantity Limit:", c.MaxClaimablePerWallet)
fmt.Println("Price:", c.Price)
fmt.Println("Wait In Seconds", c.WaitInSeconds)
Expand All @@ -76,7 +76,7 @@ var editionDropGetActiveCmd = &cobra.Command{
fmt.Printf(fmt.Sprintf("\n\nClaim Condition %d\n================\n", i))
fmt.Println("Start Time:", c.StartTime)
fmt.Println("Available:", c.AvailableSupply)
fmt.Println("Quantity:", c.MaxQuantity)
fmt.Println("Quantity:", c.MaxClaimableSupply)
fmt.Println("Quantity Limit:", c.MaxClaimablePerWallet)
fmt.Println("Price:", c.Price)
fmt.Println("Wait In Seconds", c.WaitInSeconds)
Expand Down
4 changes: 2 additions & 2 deletions cmd/thirdweb/nft_drop_commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ var nftDropGetActiveCmd = &cobra.Command{

fmt.Println("Start Time:", active.StartTime)
fmt.Println("Available:", active.AvailableSupply)
fmt.Println("Quantity:", active.MaxQuantity)
fmt.Println("Quantity:", active.MaxClaimableSupply)
fmt.Println("Quantity Limit:", active.MaxClaimablePerWallet)
fmt.Println("Price:", active.Price)
fmt.Println("Wait In Seconds", active.WaitInSeconds)
Expand All @@ -104,7 +104,7 @@ var nftDropGetActiveCmd = &cobra.Command{
fmt.Printf(fmt.Sprintf("\n\nClaim Condition %d\n================\n", i))
fmt.Println("Start Time:", c.StartTime)
fmt.Println("Available:", c.AvailableSupply)
fmt.Println("Quantity:", c.MaxQuantity)
fmt.Println("Quantity:", c.MaxClaimableSupply)
fmt.Println("Quantity Limit:", c.MaxClaimablePerWallet)
fmt.Println("Price:", c.Price)
fmt.Println("Wait In Seconds", c.WaitInSeconds)
Expand Down
32 changes: 26 additions & 6 deletions merkle/merkle_tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"math"
"reflect"
"sort"
"sync"
)

Expand Down Expand Up @@ -43,6 +44,10 @@ type Config struct {
// If true, generate a dummy node with random hash value.
// Otherwise, then the odd node situation is handled by duplicating the previous node.
NoDuplicates bool
// IMPORTANT: To match the behavior of merkletreejs, sort leaves before building tree
SortLeaves bool
// IMPORTANT: To match the behavior of merkletreejs, sort pairs before hashing them
SortPairs bool
}

// MerkleTree implements the Merkle Tree structure
Expand Down Expand Up @@ -307,6 +312,7 @@ func getDummyHash() ([]byte, error) {
return dummyBytes, nil
}

// IMPORTANT: Do not call hash function on leaves!
func (m *MerkleTree) leafGen(blocks []DataBlock) ([][]byte, error) {
var (
lenLeaves = len(blocks)
Expand All @@ -317,12 +323,17 @@ func (m *MerkleTree) leafGen(blocks []DataBlock) ([][]byte, error) {
if err != nil {
return nil, err
}
var hash []byte
if hash, err = m.HashFunc(data); err != nil {
return nil, err
}
leaves[i] = hash

leaves[i] = data
}

// IMPORTANT: To match merkletreejs, we need to sort the leaves before hashing
if m.SortLeaves {
sort.Slice(leaves, func(i, j int) bool {
return bytes.Compare(leaves[i], leaves[j]) < 0
})
}

return leaves, nil
}

Expand Down Expand Up @@ -388,7 +399,16 @@ func (m *MerkleTree) treeBuild() (err error) {
if (reflect.DeepEqual(m.tree[i][j], m.tree[i][j+1])) {
m.tree[i+1][j>>1] = m.tree[i][j]
} else {
m.tree[i+1][j>>1], err = m.HashFunc(append(m.tree[i][j], m.tree[i][j+1]...))
// IMPORTANT: To match merkletreejs, we sort the two leaves before hashing them together
if m.SortPairs {
if bytes.Compare(m.tree[i][j], m.tree[i][j+1]) < 0 {
m.tree[i+1][j>>1], err = m.HashFunc(append(m.tree[i][j], m.tree[i][j+1]...))
} else {
m.tree[i+1][j>>1], err = m.HashFunc(append(m.tree[i][j+1], m.tree[i][j]...))
}
} else {
m.tree[i+1][j>>1], err = m.HashFunc(append(m.tree[i][j], m.tree[i][j+1]...))
}
}

if err != nil {
Expand Down
37 changes: 0 additions & 37 deletions thirdweb/merkle_tree_test.go

This file was deleted.

19 changes: 14 additions & 5 deletions thirdweb/nft_drop.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,11 @@ func (drop *NFTDrop) GetClaimInfo(ctx context.Context, address string) (*ClaimIn
return nil, err
}

active, err := drop.ClaimConditions.GetActive()
if err != nil {
return nil, err
}

activeConditionIndex, err := drop.Abi.GetActiveClaimConditionId(&bind.CallOpts{})
if err != nil {
return nil, err
Expand All @@ -246,6 +251,9 @@ func (drop *NFTDrop) GetClaimInfo(ctx context.Context, address string) (*ClaimIn
if remainingClaimable.Cmp(big.NewInt(0)) < 0 {
remainingClaimable = big.NewInt(0)
}
if active.AvailableSupply.Cmp(remainingClaimable) < 0 {
remainingClaimable = active.AvailableSupply
}

return &ClaimInfo{
PricePerToken: claimVerification.Price,
Expand All @@ -262,17 +270,15 @@ func (drop *NFTDrop) GetClaimIneligibilityReasons(ctx context.Context, quantity
if strings.Contains(err.Error(), "!CONDITION") || strings.Contains(err.Error(), "no active mint condition") {
reasons = append(reasons, NoClaimConditionSet)
return reasons, nil
} else {
reasons = append(reasons, Unknown)
return reasons, nil
}
}

return reasons, err
}

activeConditionIndex, err := drop.Abi.GetActiveClaimConditionId(&bind.CallOpts{})
if err != nil {
return nil, err
}

totalClaimedInPhase, err := drop.Abi.GetSupplyClaimedByWallet(&bind.CallOpts{}, activeConditionIndex, common.HexToAddress(addressToCheck))
if err != nil {
return nil, err
Expand All @@ -282,6 +288,7 @@ func (drop *NFTDrop) GetClaimIneligibilityReasons(ctx context.Context, quantity
if active.AvailableSupply.Cmp(MaxUint256) != 0 {
if active.AvailableSupply.Cmp(big.NewInt(int64(quantity))) < 0 {
reasons = append(reasons, NotEnoughSupply)
return reasons, nil
}
}

Expand Down Expand Up @@ -365,6 +372,7 @@ func (drop *NFTDrop) GetClaimIneligibilityReasons(ctx context.Context, quantity

if balance.Cmp(totalPrice) < 0 {
reasons = append(reasons, InsufficientBalance)
return reasons, nil
}
} else {
provider := drop.Helper.GetProvider()
Expand All @@ -380,6 +388,7 @@ func (drop *NFTDrop) GetClaimIneligibilityReasons(ctx context.Context, quantity

if balance.Cmp(totalPrice) < 0 {
reasons = append(reasons, InsufficientBalance)
return reasons, nil
}
}

Expand Down
6 changes: 5 additions & 1 deletion thirdweb/sharded_merkle_tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"encoding/hex"
"encoding/json"
"fmt"
"reflect"
"strings"

Expand Down Expand Up @@ -124,7 +125,8 @@ func (tree *ShardedMerkleTree) GetProof(
uri := tree.baseUri + `/` + shardId + `.json`
raw, err := tree.storage.Get(uri)
if err != nil {
return nil, err
fmt.Println("[warning] specified address is not in merkle tree")
return nil, nil
}

err = json.Unmarshal(raw, &shardData)
Expand Down Expand Up @@ -183,6 +185,8 @@ func (tree *ShardedMerkleTree) GetProof(
config := &merkletree.Config{
Mode: merkletree.ModeProofGenAndTreeBuild,
HashFunc: calculateHash,
SortLeaves: true,
SortPairs: true,
}

merkleTree, err := merkletree.New(config, hashedEntries)
Expand Down
86 changes: 86 additions & 0 deletions thirdweb/sharded_merkle_tree_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package thirdweb

import (
"bytes"
"context"
"encoding/hex"
"encoding/json"
"fmt"
"testing"

"github.com/stretchr/testify/assert"
)

func TestMerkleTree(t *testing.T) {
sdk, err := NewThirdwebSDK("http://localhost:8545", nil)
if err != nil {
panic(err)
}

uri := "ipfs://QmeAx8aRvsYXN6mzky72b9V1HWokb271FoBmDu4tatC8hE/0"
storage := newIpfsStorage(defaultIpfsGatewayUrl)

body, err := storage.Get(uri)
if err != nil {
panic(err)
}
assert.Nil(t, err)

var info ShardedMerkleTreeInfo
if err := json.Unmarshal(body, &info); err != nil {
panic(err)
}

tree := shardedMerkleTreeFromInfo(&info, storage)
proof, err := tree.GetProof(context.Background(), "0x0000000000000000000000000000000000000000", sdk.GetProvider())
if err != nil {
panic(err)
}
assert.Nil(t, err)

fmt.Printf("%#v", proof.Proof)
}

func TestMerkleTreeEdgeCase(t *testing.T) {
sdk, err := NewThirdwebSDK("http://localhost:8545", nil)
if err != nil {
panic(err)
}

uri := "ipfs://QmacDnA4i7Za19LpE3pngwLfUtakn71ghaKKjTkM2Phzj8/0"
storage := newIpfsStorage(defaultIpfsGatewayUrl)

body, err := storage.Get(uri)
if err != nil {
panic(err)
}
assert.Nil(t, err)

var info ShardedMerkleTreeInfo
if err := json.Unmarshal(body, &info); err != nil {
panic(err)
}

tree := shardedMerkleTreeFromInfo(&info, storage)
proof, err := tree.GetProof(context.Background(), "0x9e1b8A86fFEE4a7175DAE4bDB1cC12d111Dcb3D6", sdk.GetProvider())
if err != nil {
panic(err)
}
assert.Nil(t, err)
assert.NotNil(t, proof)

correctProofs := []string{
"36f4f9e91158fce37ae8b1f3443025384d220b25db67976898893f3418722bee",
"35a5350f8ef7d3351bad08b2476dba6ec6939d501b889d98e726ae6a3e822ef4",
"8cbf083257ff0aa97fb2b28f0b9a07c3dc6e9820f89b584ef0137d50f82b7b71",
"e0be2bf7a799f716120eb3f15b6e2139e701c070b7333d5d19baf2154c2c9f95",
"91d36657024683736d0c306fcbe60f236f24dcd3c03c5c39dc48c1c0fb08418d",
}

for i, correctProof := range correctProofs {
proofBytes, err := hex.DecodeString(correctProof)
assert.Nil(t, err)

assert.Equal(t, bytes.Compare(proof.Proof[i][:], proofBytes), 0)
}
}
1 change: 0 additions & 1 deletion thirdweb/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ type ClaimConditionOutput struct {
Price *big.Int
CurrencyAddress string
CurrencyMetadata *CurrencyValue
MaxQuantity *big.Int
MerkleRootHash [32]byte
}
type Currency struct {
Expand Down

0 comments on commit 88d7951

Please sign in to comment.