@@ -9,18 +9,22 @@ use crate::{
9
9
bq_analytics:: generic_parquet_processor:: { GetTimeStamp , HasVersion , NamedTable } ,
10
10
db:: common:: models:: {
11
11
fungible_asset_models:: parquet_v2_fungible_asset_balances:: DEFAULT_AMOUNT_VALUE ,
12
- object_models:: v2_object_utils:: ObjectAggregatedDataMapping ,
12
+ object_models:: v2_object_utils:: { ObjectAggregatedDataMapping , ObjectWithMetadata } ,
13
13
token_models:: { token_utils:: TokenWriteSet , tokens:: TableHandleToOwner } ,
14
14
token_v2_models:: {
15
- parquet_v2_token_datas:: TokenDataV2 , v2_token_ownerships:: CurrentTokenOwnershipV2 ,
16
- v2_token_utils:: TokenStandard ,
15
+ parquet_v2_token_datas:: TokenDataV2 ,
16
+ v2_token_ownerships:: { CurrentTokenOwnershipV2 , NFTOwnershipV2 } ,
17
+ v2_token_utils:: { TokenStandard , TokenV2Burned , DEFAULT_OWNER_ADDRESS } ,
17
18
} ,
18
19
} ,
19
20
utils:: util:: { ensure_not_negative, standardize_address} ,
20
21
} ;
22
+ use ahash:: AHashMap ;
21
23
use allocative_derive:: Allocative ;
22
24
use anyhow:: Context ;
23
- use aptos_protos:: transaction:: v1:: { DeleteTableItem , WriteTableItem } ;
25
+ use aptos_protos:: transaction:: v1:: {
26
+ DeleteResource , DeleteTableItem , WriteResource , WriteTableItem ,
27
+ } ;
24
28
use bigdecimal:: { BigDecimal , ToPrimitive , Zero } ;
25
29
use field_count:: FieldCount ;
26
30
use parquet_derive:: ParquetRecordWriter ;
@@ -72,7 +76,6 @@ impl TokenOwnershipV2 {
72
76
object_metadatas : & ObjectAggregatedDataMapping ,
73
77
) -> anyhow:: Result < Vec < Self > > {
74
78
let mut ownerships = vec ! [ ] ;
75
- // let mut current_ownerships = AHashMap::new();
76
79
77
80
let object_data = object_metadatas
78
81
. get ( & token_data. token_data_id )
@@ -136,14 +139,76 @@ impl TokenOwnershipV2 {
136
139
Ok ( ownerships)
137
140
}
138
141
142
+ async fn get_burned_nft_v2_helper (
143
+ token_address : & str ,
144
+ txn_version : i64 ,
145
+ write_set_change_index : i64 ,
146
+ txn_timestamp : chrono:: NaiveDateTime ,
147
+ prior_nft_ownership : & AHashMap < String , NFTOwnershipV2 > ,
148
+ tokens_burned : & TokenV2Burned ,
149
+ ) -> anyhow:: Result < Option < ( Self , CurrentTokenOwnershipV2 ) > > {
150
+ let token_address = standardize_address ( token_address) ;
151
+ if let Some ( burn_event) = tokens_burned. get ( & token_address) {
152
+ // 1. Try to lookup token address in burn event mapping
153
+ let previous_owner =
154
+ if let Some ( previous_owner) = burn_event. get_previous_owner_address ( ) {
155
+ previous_owner
156
+ } else {
157
+ // 2. If it doesn't exist in burn event mapping, then it must be an old burn event that doesn't contain previous_owner.
158
+ // Do a lookup to get previous owner. This is necessary because previous owner is part of current token ownerships primary key.
159
+ match prior_nft_ownership. get ( & token_address) {
160
+ Some ( inner) => inner. owner_address . clone ( ) ,
161
+ None => DEFAULT_OWNER_ADDRESS . to_string ( ) ,
162
+ }
163
+ } ;
164
+
165
+ let token_data_id = token_address. clone ( ) ;
166
+ let storage_id = token_data_id. clone ( ) ;
167
+
168
+ return Ok ( Some ( (
169
+ Self {
170
+ txn_version,
171
+ write_set_change_index,
172
+ token_data_id : token_data_id. clone ( ) ,
173
+ property_version_v1 : LEGACY_DEFAULT_PROPERTY_VERSION ,
174
+ owner_address : Some ( previous_owner. clone ( ) ) ,
175
+ storage_id : storage_id. clone ( ) ,
176
+ amount : DEFAULT_AMOUNT_VALUE . clone ( ) ,
177
+ table_type_v1 : None ,
178
+ token_properties_mutated_v1 : None ,
179
+ is_soulbound_v2 : None , // default
180
+ token_standard : TokenStandard :: V2 . to_string ( ) ,
181
+ block_timestamp : txn_timestamp,
182
+ non_transferrable_by_owner : None , // default
183
+ } ,
184
+ CurrentTokenOwnershipV2 {
185
+ token_data_id,
186
+ property_version_v1 : BigDecimal :: zero ( ) ,
187
+ owner_address : previous_owner,
188
+ storage_id,
189
+ amount : BigDecimal :: zero ( ) ,
190
+ table_type_v1 : None ,
191
+ token_properties_mutated_v1 : None ,
192
+ is_soulbound_v2 : None , // default
193
+ token_standard : TokenStandard :: V2 . to_string ( ) ,
194
+ is_fungible_v2 : None , // default
195
+ last_transaction_version : txn_version,
196
+ last_transaction_timestamp : txn_timestamp,
197
+ non_transferrable_by_owner : None , // default
198
+ } ,
199
+ ) ) ) ;
200
+ }
201
+ Ok ( None )
202
+ }
203
+
139
204
/// We want to track tokens in any offer/claims and tokenstore
140
205
pub fn get_v1_from_delete_table_item (
141
206
table_item : & DeleteTableItem ,
142
207
txn_version : i64 ,
143
208
write_set_change_index : i64 ,
144
209
txn_timestamp : chrono:: NaiveDateTime ,
145
210
table_handle_to_owner : & TableHandleToOwner ,
146
- ) -> anyhow:: Result < Option < Self > > {
211
+ ) -> anyhow:: Result < Option < ( Self , Option < CurrentTokenOwnershipV2 > ) > > {
147
212
let table_item_data = table_item. data . as_ref ( ) . unwrap ( ) ;
148
213
149
214
let maybe_token_id = match TokenWriteSet :: from_table_item_type (
@@ -161,7 +226,7 @@ impl TokenOwnershipV2 {
161
226
let token_data_id = token_data_id_struct. to_id ( ) ;
162
227
163
228
let maybe_table_metadata = table_handle_to_owner. get ( & table_handle) ;
164
- let ( _ , owner_address, table_type) = match maybe_table_metadata {
229
+ let ( curr_token_ownership , owner_address, table_type) = match maybe_table_metadata {
165
230
Some ( tm) => {
166
231
if tm. table_type != "0x3::token::TokenStore" {
167
232
return Ok ( None ) ;
@@ -190,21 +255,24 @@ impl TokenOwnershipV2 {
190
255
None => ( None , None , None ) ,
191
256
} ;
192
257
193
- Ok ( Some ( Self {
194
- txn_version,
195
- write_set_change_index,
196
- token_data_id,
197
- property_version_v1 : token_id_struct. property_version . to_u64 ( ) . unwrap ( ) ,
198
- owner_address,
199
- storage_id : table_handle,
200
- amount : DEFAULT_AMOUNT_VALUE . clone ( ) ,
201
- table_type_v1 : table_type,
202
- token_properties_mutated_v1 : None ,
203
- is_soulbound_v2 : None ,
204
- token_standard : TokenStandard :: V1 . to_string ( ) ,
205
- block_timestamp : txn_timestamp,
206
- non_transferrable_by_owner : None ,
207
- } ) )
258
+ Ok ( Some ( (
259
+ Self {
260
+ txn_version,
261
+ write_set_change_index,
262
+ token_data_id,
263
+ property_version_v1 : token_id_struct. property_version . to_u64 ( ) . unwrap ( ) ,
264
+ owner_address,
265
+ storage_id : table_handle,
266
+ amount : DEFAULT_AMOUNT_VALUE . clone ( ) ,
267
+ table_type_v1 : table_type,
268
+ token_properties_mutated_v1 : None ,
269
+ is_soulbound_v2 : None ,
270
+ token_standard : TokenStandard :: V1 . to_string ( ) ,
271
+ block_timestamp : txn_timestamp,
272
+ non_transferrable_by_owner : None ,
273
+ } ,
274
+ curr_token_ownership,
275
+ ) ) )
208
276
} else {
209
277
Ok ( None )
210
278
}
@@ -217,7 +285,7 @@ impl TokenOwnershipV2 {
217
285
write_set_change_index : i64 ,
218
286
txn_timestamp : chrono:: NaiveDateTime ,
219
287
table_handle_to_owner : & TableHandleToOwner ,
220
- ) -> anyhow:: Result < Option < Self > > {
288
+ ) -> anyhow:: Result < Option < ( Self , Option < CurrentTokenOwnershipV2 > ) > > {
221
289
let table_item_data = table_item. data . as_ref ( ) . unwrap ( ) ;
222
290
223
291
let maybe_token = match TokenWriteSet :: from_table_item_type (
@@ -237,36 +305,204 @@ impl TokenOwnershipV2 {
237
305
let token_data_id = token_data_id_struct. to_id ( ) ;
238
306
239
307
let maybe_table_metadata = table_handle_to_owner. get ( & table_handle) ;
240
- let ( owner_address, table_type) = match maybe_table_metadata {
308
+ let ( curr_token_ownership , owner_address, table_type) = match maybe_table_metadata {
241
309
Some ( tm) => {
242
310
if tm. table_type != "0x3::token::TokenStore" {
243
311
return Ok ( None ) ;
244
312
}
245
313
let owner_address = tm. get_owner_address ( ) ;
246
- ( Some ( owner_address) , Some ( tm. table_type . clone ( ) ) )
314
+ (
315
+ Some ( CurrentTokenOwnershipV2 {
316
+ token_data_id : token_data_id. clone ( ) ,
317
+ property_version_v1 : token_id_struct. property_version . clone ( ) ,
318
+ owner_address : owner_address. clone ( ) ,
319
+ storage_id : table_handle. clone ( ) ,
320
+ amount : amount. clone ( ) ,
321
+ table_type_v1 : Some ( tm. table_type . clone ( ) ) ,
322
+ token_properties_mutated_v1 : Some ( token. token_properties . clone ( ) ) ,
323
+ is_soulbound_v2 : None ,
324
+ token_standard : TokenStandard :: V1 . to_string ( ) ,
325
+ is_fungible_v2 : None ,
326
+ last_transaction_version : txn_version,
327
+ last_transaction_timestamp : txn_timestamp,
328
+ non_transferrable_by_owner : None ,
329
+ } ) ,
330
+ Some ( owner_address) ,
331
+ Some ( tm. table_type . clone ( ) ) ,
332
+ )
247
333
} ,
248
- None => ( None , None ) ,
334
+ None => ( None , None , None ) ,
249
335
} ;
250
336
251
- Ok ( Some ( Self {
252
- txn_version,
253
- write_set_change_index,
254
- token_data_id,
255
- property_version_v1 : token_id_struct. property_version . to_u64 ( ) . unwrap ( ) ,
256
- owner_address,
257
- storage_id : table_handle,
258
- amount : amount. to_string ( ) ,
259
- table_type_v1 : table_type,
260
- token_properties_mutated_v1 : Some (
261
- canonical_json:: to_string ( & token. token_properties ) . unwrap ( ) ,
262
- ) ,
263
- is_soulbound_v2 : None ,
264
- token_standard : TokenStandard :: V1 . to_string ( ) ,
265
- block_timestamp : txn_timestamp,
266
- non_transferrable_by_owner : None ,
267
- } ) )
337
+ Ok ( Some ( (
338
+ Self {
339
+ txn_version,
340
+ write_set_change_index,
341
+ token_data_id,
342
+ property_version_v1 : token_id_struct. property_version . to_u64 ( ) . unwrap ( ) ,
343
+ owner_address,
344
+ storage_id : table_handle,
345
+ amount : amount. to_string ( ) ,
346
+ table_type_v1 : table_type,
347
+ token_properties_mutated_v1 : Some (
348
+ canonical_json:: to_string ( & token. token_properties ) . unwrap ( ) ,
349
+ ) ,
350
+ is_soulbound_v2 : None ,
351
+ token_standard : TokenStandard :: V1 . to_string ( ) ,
352
+ block_timestamp : txn_timestamp,
353
+ non_transferrable_by_owner : None ,
354
+ } ,
355
+ curr_token_ownership,
356
+ ) ) )
268
357
} else {
269
358
Ok ( None )
270
359
}
271
360
}
361
+
362
+ pub async fn get_burned_nft_v2_from_write_resource (
363
+ write_resource : & WriteResource ,
364
+ txn_version : i64 ,
365
+ write_set_change_index : i64 ,
366
+ txn_timestamp : chrono:: NaiveDateTime ,
367
+ prior_nft_ownership : & AHashMap < String , NFTOwnershipV2 > ,
368
+ tokens_burned : & TokenV2Burned ,
369
+ object_metadatas : & ObjectAggregatedDataMapping ,
370
+ ) -> anyhow:: Result < Option < ( Self , CurrentTokenOwnershipV2 ) > > {
371
+ let token_data_id = standardize_address ( & write_resource. address . to_string ( ) ) ;
372
+ if tokens_burned
373
+ . get ( & standardize_address ( & token_data_id) )
374
+ . is_some ( )
375
+ {
376
+ if let Some ( object) =
377
+ & ObjectWithMetadata :: from_write_resource ( write_resource, txn_version) ?
378
+ {
379
+ let object_core = & object. object_core ;
380
+ let owner_address = object_core. get_owner_address ( ) ;
381
+ let storage_id = token_data_id. clone ( ) ;
382
+
383
+ // is_soulbound currently means if an object is completely untransferrable
384
+ // OR if only admin can transfer. Only the former is true soulbound but
385
+ // people might already be using it with the latter meaning so let's include both.
386
+ let is_soulbound = if object_metadatas
387
+ . get ( & token_data_id)
388
+ . map ( |obj| obj. untransferable . as_ref ( ) )
389
+ . is_some ( )
390
+ {
391
+ true
392
+ } else {
393
+ !object_core. allow_ungated_transfer
394
+ } ;
395
+ let non_transferrable_by_owner = !object_core. allow_ungated_transfer ;
396
+
397
+ return Ok ( Some ( (
398
+ Self {
399
+ txn_version,
400
+ write_set_change_index,
401
+ token_data_id : token_data_id. clone ( ) ,
402
+ property_version_v1 : LEGACY_DEFAULT_PROPERTY_VERSION ,
403
+ owner_address : Some ( owner_address. clone ( ) ) ,
404
+ storage_id : storage_id. clone ( ) ,
405
+ amount : DEFAULT_AMOUNT_VALUE . clone ( ) ,
406
+ table_type_v1 : None ,
407
+ token_properties_mutated_v1 : None ,
408
+ is_soulbound_v2 : Some ( is_soulbound) ,
409
+ token_standard : TokenStandard :: V2 . to_string ( ) ,
410
+ block_timestamp : txn_timestamp,
411
+ non_transferrable_by_owner : Some ( non_transferrable_by_owner) ,
412
+ } ,
413
+ CurrentTokenOwnershipV2 {
414
+ token_data_id,
415
+ property_version_v1 : BigDecimal :: zero ( ) ,
416
+ owner_address,
417
+ storage_id,
418
+ amount : BigDecimal :: zero ( ) ,
419
+ table_type_v1 : None ,
420
+ token_properties_mutated_v1 : None ,
421
+ is_soulbound_v2 : Some ( is_soulbound) ,
422
+ token_standard : TokenStandard :: V2 . to_string ( ) ,
423
+ is_fungible_v2 : Some ( false ) ,
424
+ last_transaction_version : txn_version,
425
+ last_transaction_timestamp : txn_timestamp,
426
+ non_transferrable_by_owner : Some ( non_transferrable_by_owner) ,
427
+ } ,
428
+ ) ) ) ;
429
+ } else {
430
+ return Self :: get_burned_nft_v2_helper (
431
+ & token_data_id,
432
+ txn_version,
433
+ write_set_change_index,
434
+ txn_timestamp,
435
+ prior_nft_ownership,
436
+ tokens_burned,
437
+ )
438
+ . await ;
439
+ }
440
+ }
441
+ Ok ( None )
442
+ }
443
+
444
+ pub fn get_burned_nft_v2_from_delete_resource (
445
+ delete_resource : & DeleteResource ,
446
+ txn_version : i64 ,
447
+ write_set_change_index : i64 ,
448
+ txn_timestamp : chrono:: NaiveDateTime ,
449
+ prior_nft_ownership : & AHashMap < String , NFTOwnershipV2 > ,
450
+ tokens_burned : & TokenV2Burned ,
451
+ ) -> anyhow:: Result < Option < ( Self , CurrentTokenOwnershipV2 ) > > {
452
+ let token_address = standardize_address ( & delete_resource. address . to_string ( ) ) ;
453
+ let token_address = standardize_address ( & token_address) ;
454
+ if let Some ( burn_event) = tokens_burned. get ( & token_address) {
455
+ // 1. Try to lookup token address in burn event mapping
456
+ let previous_owner =
457
+ if let Some ( previous_owner) = burn_event. get_previous_owner_address ( ) {
458
+ previous_owner
459
+ } else {
460
+ // 2. If it doesn't exist in burn event mapping, then it must be an old burn event that doesn't contain previous_owner.
461
+ // Do a lookup to get previous owner. This is necessary because previous owner is part of current token ownerships primary key.
462
+ match prior_nft_ownership. get ( & token_address) {
463
+ Some ( inner) => inner. owner_address . clone ( ) ,
464
+ None => {
465
+ DEFAULT_OWNER_ADDRESS . to_string ( ) // we don't want to query db to get the previous owner for parquet.
466
+ } ,
467
+ }
468
+ } ;
469
+
470
+ let token_data_id = token_address. clone ( ) ;
471
+ let storage_id = token_data_id. clone ( ) ;
472
+
473
+ return Ok ( Some ( (
474
+ Self {
475
+ txn_version,
476
+ write_set_change_index,
477
+ token_data_id : token_data_id. clone ( ) ,
478
+ property_version_v1 : LEGACY_DEFAULT_PROPERTY_VERSION ,
479
+ owner_address : Some ( previous_owner. clone ( ) ) ,
480
+ storage_id : storage_id. clone ( ) ,
481
+ amount : DEFAULT_AMOUNT_VALUE . clone ( ) ,
482
+ table_type_v1 : None ,
483
+ token_properties_mutated_v1 : None ,
484
+ is_soulbound_v2 : None , // default
485
+ token_standard : TokenStandard :: V2 . to_string ( ) ,
486
+ block_timestamp : txn_timestamp,
487
+ non_transferrable_by_owner : None , // default
488
+ } ,
489
+ CurrentTokenOwnershipV2 {
490
+ token_data_id,
491
+ property_version_v1 : BigDecimal :: zero ( ) ,
492
+ owner_address : previous_owner,
493
+ storage_id,
494
+ amount : BigDecimal :: zero ( ) ,
495
+ table_type_v1 : None ,
496
+ token_properties_mutated_v1 : None ,
497
+ is_soulbound_v2 : None , // default
498
+ token_standard : TokenStandard :: V2 . to_string ( ) ,
499
+ is_fungible_v2 : None , // default
500
+ last_transaction_version : txn_version,
501
+ last_transaction_timestamp : txn_timestamp,
502
+ non_transferrable_by_owner : None , // default
503
+ } ,
504
+ ) ) ) ;
505
+ }
506
+ Ok ( None )
507
+ }
272
508
}
0 commit comments