Repository: algorand/indexer
Pull request #410 (2021-03-31 17:44:48)

Creator holding with Total = 0 is valid.
## Summary

It is valid to create an asset with Total = 0 and in this case algod will create an asset holding on the creator with Amount = 0. Indexer was skipping the zero balance asset holding, this PR changes that behavior to match algod.

## Test Plan

Added tests.


Patch:
diff --git a/accounting/accounting.go b/accounting/accounting.go
index c47ccf3..4d110cb 100644
--- a/accounting/accounting.go
+++ b/accounting/accounting.go
@@ -360,10 +360,10 @@ func (accounting *State) AddTransaction(txnr *idb.TxnRow) (err error) {
 					accounting.defaultFrozen[assetID] = stxn.Txn.AssetParams.DefaultFrozen
 				}
 
-				// Initial creation, give all initial value to creator. Ignore DefaultFrozen.
-				if stxn.Txn.AssetParams.Total != 0 {
-					accounting.updateAsset(stxn.Txn.Sender, assetID, stxn.Txn.AssetParams.Total, 0, false)
-				}
+				// Initial creation, give all initial value to creator.
+				// Ignore DefaultFrozen.
+				// Total = 0 is valid.
+				accounting.updateAsset(stxn.Txn.Sender, assetID, stxn.Txn.AssetParams.Total, 0, false)
 			}
 		}
 	case atypes.AssetTransferTx:


Test patch:
diff --git a/accounting/accounting_test.go b/accounting/accounting_test.go
index 31ef1b9..468589e 100644
--- a/accounting/accounting_test.go
+++ b/accounting/accounting_test.go
@@ -153,3 +153,28 @@ func TestAssetCloseWithAmountReopenPay(t *testing.T) {
 	// Subround 2 is empty
 	assert.Len(t, state.RoundUpdates.AssetUpdates[1], 0)
 }
+
+// TestCreateAssetWithTotalZero checks that we can create an asset with total = 0
+func TestCreateAssetWithTotalZero(t *testing.T) {
+	assetid := uint64(2222)
+	total := uint64(0)
+
+	///////////
+	// Given // Empty state.
+	///////////
+	state := GetAccounting()
+
+	//////////
+	// When // We add a create asset transaction with total = 0.
+	//////////
+	_, createAsset := test.MakeAssetConfigOrPanic(test.Round, 0, assetid, total, uint64(6), false, "empty-asset", "empty", "http://empty.com", test.AccountA)
+	state.AddTransaction(createAsset)
+
+	//////////
+	// Then // There should be deltas.
+	//////////
+	// Config + Transfer
+	assert.Len(t, state.RoundUpdates.AssetUpdates, 2)
+	assert.True(t, state.RoundUpdates.AssetUpdates[0][test.AccountA][0].Config.IsNew)
+	assert.Equal(t, state.RoundUpdates.AssetUpdates[1][test.AccountA][0].Transfer.Delta.Int64(), int64(0))
+}
\ No newline at end of file
diff --git a/idb/postgres/postgres_integration_test.go b/idb/postgres/postgres_integration_test.go
index 1eb2e09..b86dc4d 100644
--- a/idb/postgres/postgres_integration_test.go
+++ b/idb/postgres/postgres_integration_test.go
@@ -632,6 +632,7 @@ func TestRekeyToItselfHasNotBeenRekeyed(t *testing.T) {
 	assert.NoError(t, err, "failed to commit")
 }
 
+// TestIgnoreDefaultFrozenConfigUpdate the creator asset holding should ignore default-frozen = true.
 func TestIgnoreDefaultFrozenConfigUpdate(t *testing.T) {
 	db, connStr, shutdownFunc := setupPostgres(t)
 	defer shutdownFunc()
@@ -668,4 +669,37 @@ func TestIgnoreDefaultFrozenConfigUpdate(t *testing.T) {
 	// default-frozen = true
 	assertAccountAsset(t, db, test.AccountA, assetid, false, total)
 	assertAccountAsset(t, db, test.AccountB, assetid, false, 0)
-}
\ No newline at end of file
+}
+
+// TestZeroTotalAssetCreate tests that the asset holding with total of 0 is created.
+func TestZeroTotalAssetCreate(t *testing.T) {
+	db, connStr, shutdownFunc := setupPostgres(t)
+	defer shutdownFunc()
+
+	pdb, err := idb.IndexerDbByName("postgres", connStr, nil, nil)
+	assert.NoError(t, err)
+
+	assetid := uint64(2222)
+	total := uint64(0)
+
+	///////////
+	// Given // A new asset with total = 0.
+	///////////
+	_, createAsset := test.MakeAssetConfigOrPanic(test.Round, 0, assetid, total, uint64(6), false, "icicles", "frozen coin", "http://antarctica.com", test.AccountA)
+
+	cache, err := pdb.GetDefaultFrozen()
+	assert.NoError(t, err)
+	state := getAccounting(test.Round, cache)
+	state.AddTransaction(createAsset)
+
+	//////////
+	// When // We commit the round accounting to the database.
+	//////////
+	err = pdb.CommitRoundAccounting(state.RoundUpdates, test.Round, &itypes.Block{})
+	assert.NoError(t, err, "failed to commit")
+
+	//////////
+	// Then // Make sure the creator has an asset holding with amount = 0.
+	//////////
+	assertAccountAsset(t, db, test.AccountA, assetid, false, 0)
+}