Skip to content

Commit 945e3c7

Browse files
committed
supplycommit: add CalcTotalOutstandingSupply helper function
Adds a helper function to compute the total outstanding asset amount supply given a set of supply subtrees. This function will be used in a subsequent commit that modifies the FetchSupplyCommit RPC endpoint.
1 parent 7e6ef7b commit 945e3c7

File tree

2 files changed

+254
-0
lines changed

2 files changed

+254
-0
lines changed

universe/supplycommit/util.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package supplycommit
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/lightningnetwork/lnd/fn/v2"
8+
)
9+
10+
// CalcTotalOutstandingSupply calculates the total outstanding supply from the
11+
// given supply subtrees.
12+
func CalcTotalOutstandingSupply(ctx context.Context,
13+
supplySubtrees SupplyTrees) fn.Result[uint64] {
14+
15+
var total uint64
16+
17+
// Add the total minted amount if we have a mint tree.
18+
if mintTree, ok := supplySubtrees[MintTreeType]; ok {
19+
root, err := mintTree.Root(ctx)
20+
if err != nil {
21+
return fn.Err[uint64](fmt.Errorf("unable to "+
22+
"extract mint tree root: %w", err))
23+
}
24+
25+
total = root.NodeSum()
26+
}
27+
28+
// Return early if there's no minted supply, ignore the other subtrees.
29+
if total == 0 {
30+
return fn.Ok[uint64](0)
31+
}
32+
33+
// Subtract the total burned amount if we have a burn tree.
34+
if burnTree, ok := supplySubtrees[BurnTreeType]; ok {
35+
root, err := burnTree.Root(ctx)
36+
if err != nil {
37+
return fn.Err[uint64](fmt.Errorf("unable to "+
38+
"extract burn tree root: %w", err))
39+
}
40+
41+
burned := root.NodeSum()
42+
if burned > total {
43+
return fn.Err[uint64](fmt.Errorf("total burned %d "+
44+
"exceeds total outstanding %d", burned, total))
45+
}
46+
47+
total -= burned
48+
}
49+
50+
// Subtract the total ignored amount if we have an ignore tree.
51+
if ignoreTree, ok := supplySubtrees[IgnoreTreeType]; ok {
52+
root, err := ignoreTree.Root(ctx)
53+
if err != nil {
54+
return fn.Err[uint64](fmt.Errorf("unable to "+
55+
"extract ignore tree root: %w", err))
56+
}
57+
58+
ignored := root.NodeSum()
59+
if ignored > total {
60+
return fn.Err[uint64](fmt.Errorf("total ignored %d "+
61+
"exceeds total outstanding %d", ignored, total))
62+
}
63+
64+
total -= ignored
65+
}
66+
67+
return fn.Ok[uint64](total)
68+
}

universe/supplycommit/util_test.go

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
package supplycommit
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"testing"
7+
8+
"github.com/lightninglabs/taproot-assets/mssmt"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
// createTreeWithSum creates an in-memory mssmt tree with the specified sum.
13+
// If sum is 0, returns an empty tree.
14+
func createTreeWithSum(sum uint64) mssmt.Tree {
15+
store := mssmt.NewDefaultStore()
16+
tree := mssmt.NewCompactedTree(store)
17+
18+
if sum > 0 {
19+
// Insert a leaf with the desired sum.
20+
//
21+
// Use sum to create unique key.
22+
key := [32]byte{byte(sum % 256)}
23+
leaf := mssmt.NewLeafNode(
24+
[]byte(fmt.Sprintf("value-%d", sum)), sum,
25+
)
26+
newTree, _ := tree.Insert(context.Background(), key, leaf)
27+
return newTree
28+
}
29+
30+
return tree
31+
}
32+
33+
// TestCalcTotalOutstandingSupply tests the CalcTotalOutstandingSupply function
34+
// with various combinations of supply trees.
35+
func TestCalcTotalOutstandingSupply(t *testing.T) {
36+
t.Parallel()
37+
38+
ctx := context.Background()
39+
40+
testCases := []struct {
41+
name string
42+
supplyTrees SupplyTrees
43+
expectedResult uint64
44+
expectedError string
45+
}{
46+
{
47+
name: "empty supply trees",
48+
supplyTrees: SupplyTrees{},
49+
expectedResult: 0,
50+
expectedError: "",
51+
},
52+
{
53+
name: "only mint tree with zero sum",
54+
supplyTrees: SupplyTrees{
55+
MintTreeType: createTreeWithSum(0),
56+
},
57+
expectedResult: 0,
58+
expectedError: "",
59+
},
60+
{
61+
name: "only mint tree with positive sum",
62+
supplyTrees: SupplyTrees{
63+
MintTreeType: createTreeWithSum(1000),
64+
},
65+
expectedResult: 1000,
66+
expectedError: "",
67+
},
68+
{
69+
name: "mint and burn trees",
70+
supplyTrees: SupplyTrees{
71+
MintTreeType: createTreeWithSum(1000),
72+
BurnTreeType: createTreeWithSum(300),
73+
},
74+
expectedResult: 700,
75+
expectedError: "",
76+
},
77+
{
78+
name: "mint and ignore trees",
79+
supplyTrees: SupplyTrees{
80+
MintTreeType: createTreeWithSum(1000),
81+
IgnoreTreeType: createTreeWithSum(200),
82+
},
83+
expectedResult: 800,
84+
expectedError: "",
85+
},
86+
{
87+
name: "all three tree types",
88+
supplyTrees: SupplyTrees{
89+
MintTreeType: createTreeWithSum(1000),
90+
BurnTreeType: createTreeWithSum(200),
91+
IgnoreTreeType: createTreeWithSum(100),
92+
},
93+
expectedResult: 700,
94+
expectedError: "",
95+
},
96+
{
97+
name: "burned amount exceeds total minted",
98+
supplyTrees: SupplyTrees{
99+
MintTreeType: createTreeWithSum(500),
100+
BurnTreeType: createTreeWithSum(600),
101+
},
102+
expectedResult: 0,
103+
expectedError: "total burned 600 exceeds total " +
104+
"outstanding 500",
105+
},
106+
{
107+
name: "ignored amount exceeds remaining supply",
108+
supplyTrees: SupplyTrees{
109+
MintTreeType: createTreeWithSum(1000),
110+
BurnTreeType: createTreeWithSum(200),
111+
IgnoreTreeType: createTreeWithSum(900),
112+
},
113+
expectedResult: 0,
114+
expectedError: "total ignored 900 exceeds total " +
115+
"outstanding 800",
116+
},
117+
{
118+
name: "burn exactly equals mint",
119+
supplyTrees: SupplyTrees{
120+
MintTreeType: createTreeWithSum(500),
121+
BurnTreeType: createTreeWithSum(500),
122+
},
123+
expectedResult: 0,
124+
expectedError: "",
125+
},
126+
{
127+
name: "ignore exactly equals remaining supply",
128+
supplyTrees: SupplyTrees{
129+
MintTreeType: createTreeWithSum(1000),
130+
BurnTreeType: createTreeWithSum(300),
131+
IgnoreTreeType: createTreeWithSum(700),
132+
},
133+
expectedResult: 0,
134+
expectedError: "",
135+
},
136+
{
137+
name: "only burn tree (no mint)",
138+
supplyTrees: SupplyTrees{
139+
BurnTreeType: createTreeWithSum(100),
140+
},
141+
expectedResult: 0,
142+
expectedError: "",
143+
},
144+
{
145+
name: "only ignore tree (no mint)",
146+
supplyTrees: SupplyTrees{
147+
IgnoreTreeType: createTreeWithSum(100),
148+
},
149+
expectedResult: 0,
150+
expectedError: "",
151+
},
152+
}
153+
154+
for idx := range testCases {
155+
tc := testCases[idx]
156+
157+
t.Run(tc.name, func(t *testing.T) {
158+
t.Parallel()
159+
160+
result := CalcTotalOutstandingSupply(
161+
ctx, tc.supplyTrees,
162+
)
163+
164+
if tc.expectedError != "" {
165+
require.True(
166+
t, result.IsErr(),
167+
"expected error but got success",
168+
)
169+
err := result.Err()
170+
require.Contains(
171+
t, err.Error(), tc.expectedError,
172+
)
173+
174+
return
175+
}
176+
177+
require.True(
178+
t, result.IsOk(),
179+
"expected success but got error: %v",
180+
result.Err(),
181+
)
182+
actual := result.UnwrapOr(0)
183+
require.Equal(t, tc.expectedResult, actual)
184+
})
185+
}
186+
}

0 commit comments

Comments
 (0)