github.com/iotexproject/iotex-core@v1.14.1-rc1/blockindex/contractstaking/event_handler.go (about) 1 // Copyright (c) 2023 IoTeX Foundation 2 // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability 3 // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed. 4 // This source code is governed by Apache License 2.0 that can be found in the LICENSE file. 5 6 package contractstaking 7 8 import ( 9 "context" 10 "strings" 11 12 "github.com/ethereum/go-ethereum/accounts/abi" 13 "github.com/ethereum/go-ethereum/common" 14 "github.com/pkg/errors" 15 16 "github.com/iotexproject/iotex-address/address" 17 18 "github.com/iotexproject/iotex-core/action" 19 "github.com/iotexproject/iotex-core/blockchain/block" 20 "github.com/iotexproject/iotex-core/db/batch" 21 ) 22 23 const ( 24 // StakingContractABI is the ABI of system staking contract 25 StakingContractABI = `[ 26 { 27 "anonymous": false, 28 "inputs": [ 29 { 30 "indexed": true, 31 "internalType": "address", 32 "name": "owner", 33 "type": "address" 34 }, 35 { 36 "indexed": true, 37 "internalType": "address", 38 "name": "approved", 39 "type": "address" 40 }, 41 { 42 "indexed": true, 43 "internalType": "uint256", 44 "name": "tokenId", 45 "type": "uint256" 46 } 47 ], 48 "name": "Approval", 49 "type": "event" 50 }, 51 { 52 "anonymous": false, 53 "inputs": [ 54 { 55 "indexed": true, 56 "internalType": "address", 57 "name": "owner", 58 "type": "address" 59 }, 60 { 61 "indexed": true, 62 "internalType": "address", 63 "name": "operator", 64 "type": "address" 65 }, 66 { 67 "indexed": false, 68 "internalType": "bool", 69 "name": "approved", 70 "type": "bool" 71 } 72 ], 73 "name": "ApprovalForAll", 74 "type": "event" 75 }, 76 { 77 "anonymous": false, 78 "inputs": [ 79 { 80 "indexed": false, 81 "internalType": "uint256", 82 "name": "amount", 83 "type": "uint256" 84 }, 85 { 86 "indexed": false, 87 "internalType": "uint256", 88 "name": "duration", 89 "type": "uint256" 90 } 91 ], 92 "name": "BucketTypeActivated", 93 "type": "event" 94 }, 95 { 96 "anonymous": false, 97 "inputs": [ 98 { 99 "indexed": false, 100 "internalType": "uint256", 101 "name": "amount", 102 "type": "uint256" 103 }, 104 { 105 "indexed": false, 106 "internalType": "uint256", 107 "name": "duration", 108 "type": "uint256" 109 } 110 ], 111 "name": "BucketTypeDeactivated", 112 "type": "event" 113 }, 114 { 115 "anonymous": false, 116 "inputs": [ 117 { 118 "indexed": true, 119 "internalType": "uint256", 120 "name": "tokenId", 121 "type": "uint256" 122 }, 123 { 124 "indexed": false, 125 "internalType": "address", 126 "name": "newDelegate", 127 "type": "address" 128 } 129 ], 130 "name": "DelegateChanged", 131 "type": "event" 132 }, 133 { 134 "anonymous": false, 135 "inputs": [ 136 { 137 "indexed": true, 138 "internalType": "uint256", 139 "name": "tokenId", 140 "type": "uint256" 141 }, 142 { 143 "indexed": false, 144 "internalType": "uint256", 145 "name": "duration", 146 "type": "uint256" 147 } 148 ], 149 "name": "Locked", 150 "type": "event" 151 }, 152 { 153 "anonymous": false, 154 "inputs": [ 155 { 156 "indexed": false, 157 "internalType": "uint256[]", 158 "name": "tokenIds", 159 "type": "uint256[]" 160 }, 161 { 162 "indexed": false, 163 "internalType": "uint256", 164 "name": "amount", 165 "type": "uint256" 166 }, 167 { 168 "indexed": false, 169 "internalType": "uint256", 170 "name": "duration", 171 "type": "uint256" 172 } 173 ], 174 "name": "Merged", 175 "type": "event" 176 }, 177 { 178 "anonymous": false, 179 "inputs": [ 180 { 181 "indexed": true, 182 "internalType": "address", 183 "name": "previousOwner", 184 "type": "address" 185 }, 186 { 187 "indexed": true, 188 "internalType": "address", 189 "name": "newOwner", 190 "type": "address" 191 } 192 ], 193 "name": "OwnershipTransferred", 194 "type": "event" 195 }, 196 { 197 "anonymous": false, 198 "inputs": [ 199 { 200 "indexed": false, 201 "internalType": "address", 202 "name": "account", 203 "type": "address" 204 } 205 ], 206 "name": "Paused", 207 "type": "event" 208 }, 209 { 210 "anonymous": false, 211 "inputs": [ 212 { 213 "indexed": true, 214 "internalType": "uint256", 215 "name": "tokenId", 216 "type": "uint256" 217 }, 218 { 219 "indexed": false, 220 "internalType": "address", 221 "name": "delegate", 222 "type": "address" 223 }, 224 { 225 "indexed": false, 226 "internalType": "uint256", 227 "name": "amount", 228 "type": "uint256" 229 }, 230 { 231 "indexed": false, 232 "internalType": "uint256", 233 "name": "duration", 234 "type": "uint256" 235 } 236 ], 237 "name": "Staked", 238 "type": "event" 239 }, 240 { 241 "anonymous": false, 242 "inputs": [ 243 { 244 "indexed": true, 245 "internalType": "address", 246 "name": "from", 247 "type": "address" 248 }, 249 { 250 "indexed": true, 251 "internalType": "address", 252 "name": "to", 253 "type": "address" 254 }, 255 { 256 "indexed": true, 257 "internalType": "uint256", 258 "name": "tokenId", 259 "type": "uint256" 260 } 261 ], 262 "name": "Transfer", 263 "type": "event" 264 }, 265 { 266 "anonymous": false, 267 "inputs": [ 268 { 269 "indexed": true, 270 "internalType": "uint256", 271 "name": "tokenId", 272 "type": "uint256" 273 } 274 ], 275 "name": "Unlocked", 276 "type": "event" 277 }, 278 { 279 "anonymous": false, 280 "inputs": [ 281 { 282 "indexed": false, 283 "internalType": "address", 284 "name": "account", 285 "type": "address" 286 } 287 ], 288 "name": "Unpaused", 289 "type": "event" 290 }, 291 { 292 "anonymous": false, 293 "inputs": [ 294 { 295 "indexed": true, 296 "internalType": "uint256", 297 "name": "tokenId", 298 "type": "uint256" 299 } 300 ], 301 "name": "Unstaked", 302 "type": "event" 303 }, 304 { 305 "anonymous": false, 306 "inputs": [ 307 { 308 "indexed": true, 309 "internalType": "uint256", 310 "name": "tokenId", 311 "type": "uint256" 312 }, 313 { 314 "indexed": true, 315 "internalType": "address", 316 "name": "recipient", 317 "type": "address" 318 } 319 ], 320 "name": "Withdrawal", 321 "type": "event" 322 }, 323 { 324 "anonymous": false, 325 "inputs": [ 326 { 327 "indexed": true, 328 "internalType": "uint256", 329 "name": "tokenId", 330 "type": "uint256" 331 }, 332 { 333 "indexed": false, 334 "internalType": "uint256", 335 "name": "amount", 336 "type": "uint256" 337 }, 338 { 339 "indexed": false, 340 "internalType": "uint256", 341 "name": "duration", 342 "type": "uint256" 343 } 344 ], 345 "name": "BucketExpanded", 346 "type": "event" 347 } 348 ]` 349 ) 350 351 // contractStakingEventHandler handles events from staking contract 352 type contractStakingEventHandler struct { 353 dirty *contractStakingDirty 354 tokenOwner map[uint64]address.Address 355 } 356 357 var ( 358 _stakingInterface abi.ABI 359 ) 360 361 func init() { 362 var err error 363 _stakingInterface, err = abi.JSON(strings.NewReader(StakingContractABI)) 364 if err != nil { 365 panic(err) 366 } 367 } 368 369 func newContractStakingEventHandler(cache *contractStakingCache) *contractStakingEventHandler { 370 dirty := newContractStakingDirty(cache) 371 return &contractStakingEventHandler{ 372 dirty: dirty, 373 tokenOwner: make(map[uint64]address.Address), 374 } 375 } 376 377 func (eh *contractStakingEventHandler) HandleEvent(ctx context.Context, blk *block.Block, log *action.Log) error { 378 // get event abi 379 abiEvent, err := _stakingInterface.EventByID(common.Hash(log.Topics[0])) 380 if err != nil { 381 return errors.Wrapf(err, "get event abi from topic %v failed", log.Topics[0]) 382 } 383 384 // unpack event data 385 event, err := unpackEventParam(abiEvent, log) 386 if err != nil { 387 return err 388 } 389 390 // handle different kinds of event 391 switch abiEvent.Name { 392 case "BucketTypeActivated": 393 return eh.handleBucketTypeActivatedEvent(event, blk.Height()) 394 case "BucketTypeDeactivated": 395 return eh.handleBucketTypeDeactivatedEvent(event, blk.Height()) 396 case "Staked": 397 return eh.handleStakedEvent(event, blk.Height()) 398 case "Locked": 399 return eh.handleLockedEvent(event) 400 case "Unlocked": 401 return eh.handleUnlockedEvent(event, blk.Height()) 402 case "Unstaked": 403 return eh.handleUnstakedEvent(event, blk.Height()) 404 case "Merged": 405 return eh.handleMergedEvent(event) 406 case "BucketExpanded": 407 return eh.handleBucketExpandedEvent(event) 408 case "DelegateChanged": 409 return eh.handleDelegateChangedEvent(event) 410 case "Withdrawal": 411 return eh.handleWithdrawalEvent(event) 412 case "Transfer": 413 return eh.handleTransferEvent(event) 414 case "Approval", "ApprovalForAll", "OwnershipTransferred", "Paused", "Unpaused": 415 // not require handling events 416 return nil 417 default: 418 return errors.Errorf("unknown event name %s", abiEvent.Name) 419 } 420 } 421 422 func (eh *contractStakingEventHandler) Result() (batch.KVStoreBatch, *contractStakingDelta) { 423 return eh.dirty.finalize() 424 } 425 426 func (eh *contractStakingEventHandler) handleTransferEvent(event eventParam) error { 427 to, err := event.IndexedFieldAddress("to") 428 if err != nil { 429 return err 430 } 431 tokenIDParam, err := event.IndexedFieldUint256("tokenId") 432 if err != nil { 433 return err 434 } 435 436 tokenID := tokenIDParam.Uint64() 437 // cache token owner for stake event 438 eh.tokenOwner[tokenID] = to 439 // update bucket owner if token exists 440 if bi, ok := eh.dirty.getBucketInfo(tokenID); ok { 441 bi.Owner = to 442 return eh.dirty.updateBucketInfo(tokenID, bi) 443 } 444 445 return nil 446 } 447 448 func (eh *contractStakingEventHandler) handleBucketTypeActivatedEvent(event eventParam, height uint64) error { 449 amountParam, err := event.FieldUint256("amount") 450 if err != nil { 451 return err 452 } 453 durationParam, err := event.FieldUint256("duration") 454 if err != nil { 455 return err 456 } 457 458 bt := BucketType{ 459 Amount: amountParam, 460 Duration: durationParam.Uint64(), 461 ActivatedAt: height, 462 } 463 return eh.dirty.putBucketType(&bt) 464 } 465 466 func (eh *contractStakingEventHandler) handleBucketTypeDeactivatedEvent(event eventParam, height uint64) error { 467 amountParam, err := event.FieldUint256("amount") 468 if err != nil { 469 return err 470 } 471 durationParam, err := event.FieldUint256("duration") 472 if err != nil { 473 return err 474 } 475 476 id, bt, ok := eh.dirty.matchBucketType(amountParam, durationParam.Uint64()) 477 if !ok { 478 return errors.Wrapf(errBucketTypeNotExist, "amount %d, duration %d", amountParam.Int64(), durationParam.Uint64()) 479 } 480 bt.ActivatedAt = maxBlockNumber 481 return eh.dirty.updateBucketType(id, bt) 482 } 483 484 func (eh *contractStakingEventHandler) handleStakedEvent(event eventParam, height uint64) error { 485 tokenIDParam, err := event.IndexedFieldUint256("tokenId") 486 if err != nil { 487 return err 488 } 489 delegateParam, err := event.FieldAddress("delegate") 490 if err != nil { 491 return err 492 } 493 amountParam, err := event.FieldUint256("amount") 494 if err != nil { 495 return err 496 } 497 durationParam, err := event.FieldUint256("duration") 498 if err != nil { 499 return err 500 } 501 502 btIdx, _, ok := eh.dirty.matchBucketType(amountParam, durationParam.Uint64()) 503 if !ok { 504 return errors.Wrapf(errBucketTypeNotExist, "amount %d, duration %d", amountParam.Int64(), durationParam.Uint64()) 505 } 506 owner, ok := eh.tokenOwner[tokenIDParam.Uint64()] 507 if !ok { 508 return errors.Errorf("no owner for token id %d", tokenIDParam.Uint64()) 509 } 510 bucket := bucketInfo{ 511 TypeIndex: btIdx, 512 Delegate: delegateParam, 513 Owner: owner, 514 CreatedAt: height, 515 UnlockedAt: maxBlockNumber, 516 UnstakedAt: maxBlockNumber, 517 } 518 return eh.dirty.addBucketInfo(tokenIDParam.Uint64(), &bucket) 519 } 520 521 func (eh *contractStakingEventHandler) handleLockedEvent(event eventParam) error { 522 tokenIDParam, err := event.IndexedFieldUint256("tokenId") 523 if err != nil { 524 return err 525 } 526 durationParam, err := event.FieldUint256("duration") 527 if err != nil { 528 return err 529 } 530 531 b, ok := eh.dirty.getBucketInfo(tokenIDParam.Uint64()) 532 if !ok { 533 return errors.Wrapf(ErrBucketNotExist, "token id %d", tokenIDParam.Uint64()) 534 } 535 bt, ok := eh.dirty.getBucketType(b.TypeIndex) 536 if !ok { 537 return errors.Wrapf(errBucketTypeNotExist, "id %d", b.TypeIndex) 538 } 539 newBtIdx, _, ok := eh.dirty.matchBucketType(bt.Amount, durationParam.Uint64()) 540 if !ok { 541 return errors.Wrapf(errBucketTypeNotExist, "amount %v, duration %d", bt.Amount, durationParam.Uint64()) 542 } 543 b.TypeIndex = newBtIdx 544 b.UnlockedAt = maxBlockNumber 545 return eh.dirty.updateBucketInfo(tokenIDParam.Uint64(), b) 546 } 547 548 func (eh *contractStakingEventHandler) handleUnlockedEvent(event eventParam, height uint64) error { 549 tokenIDParam, err := event.IndexedFieldUint256("tokenId") 550 if err != nil { 551 return err 552 } 553 554 b, ok := eh.dirty.getBucketInfo(tokenIDParam.Uint64()) 555 if !ok { 556 return errors.Wrapf(ErrBucketNotExist, "token id %d", tokenIDParam.Uint64()) 557 } 558 b.UnlockedAt = height 559 return eh.dirty.updateBucketInfo(tokenIDParam.Uint64(), b) 560 } 561 562 func (eh *contractStakingEventHandler) handleUnstakedEvent(event eventParam, height uint64) error { 563 tokenIDParam, err := event.IndexedFieldUint256("tokenId") 564 if err != nil { 565 return err 566 } 567 568 b, ok := eh.dirty.getBucketInfo(tokenIDParam.Uint64()) 569 if !ok { 570 return errors.Wrapf(ErrBucketNotExist, "token id %d", tokenIDParam.Uint64()) 571 } 572 b.UnstakedAt = height 573 return eh.dirty.updateBucketInfo(tokenIDParam.Uint64(), b) 574 } 575 576 func (eh *contractStakingEventHandler) handleMergedEvent(event eventParam) error { 577 tokenIDsParam, err := event.FieldUint256Slice("tokenIds") 578 if err != nil { 579 return err 580 } 581 amountParam, err := event.FieldUint256("amount") 582 if err != nil { 583 return err 584 } 585 durationParam, err := event.FieldUint256("duration") 586 if err != nil { 587 return err 588 } 589 590 // merge to the first bucket 591 btIdx, _, ok := eh.dirty.matchBucketType(amountParam, durationParam.Uint64()) 592 if !ok { 593 return errors.Wrapf(errBucketTypeNotExist, "amount %d, duration %d", amountParam.Int64(), durationParam.Uint64()) 594 } 595 b, ok := eh.dirty.getBucketInfo(tokenIDsParam[0].Uint64()) 596 if !ok { 597 return errors.Wrapf(ErrBucketNotExist, "token id %d", tokenIDsParam[0].Uint64()) 598 } 599 b.TypeIndex = btIdx 600 b.UnlockedAt = maxBlockNumber 601 for i := 1; i < len(tokenIDsParam); i++ { 602 if err = eh.dirty.deleteBucketInfo(tokenIDsParam[i].Uint64()); err != nil { 603 return err 604 } 605 } 606 return eh.dirty.updateBucketInfo(tokenIDsParam[0].Uint64(), b) 607 } 608 609 func (eh *contractStakingEventHandler) handleBucketExpandedEvent(event eventParam) error { 610 tokenIDParam, err := event.IndexedFieldUint256("tokenId") 611 if err != nil { 612 return err 613 } 614 amountParam, err := event.FieldUint256("amount") 615 if err != nil { 616 return err 617 } 618 durationParam, err := event.FieldUint256("duration") 619 if err != nil { 620 return err 621 } 622 623 b, ok := eh.dirty.getBucketInfo(tokenIDParam.Uint64()) 624 if !ok { 625 return errors.Wrapf(ErrBucketNotExist, "token id %d", tokenIDParam.Uint64()) 626 } 627 newBtIdx, _, ok := eh.dirty.matchBucketType(amountParam, durationParam.Uint64()) 628 if !ok { 629 return errors.Wrapf(errBucketTypeNotExist, "amount %d, duration %d", amountParam.Int64(), durationParam.Uint64()) 630 } 631 b.TypeIndex = newBtIdx 632 return eh.dirty.updateBucketInfo(tokenIDParam.Uint64(), b) 633 } 634 635 func (eh *contractStakingEventHandler) handleDelegateChangedEvent(event eventParam) error { 636 tokenIDParam, err := event.IndexedFieldUint256("tokenId") 637 if err != nil { 638 return err 639 } 640 delegateParam, err := event.FieldAddress("newDelegate") 641 if err != nil { 642 return err 643 } 644 645 b, ok := eh.dirty.getBucketInfo(tokenIDParam.Uint64()) 646 if !ok { 647 return errors.Wrapf(ErrBucketNotExist, "token id %d", tokenIDParam.Uint64()) 648 } 649 b.Delegate = delegateParam 650 return eh.dirty.updateBucketInfo(tokenIDParam.Uint64(), b) 651 } 652 653 func (eh *contractStakingEventHandler) handleWithdrawalEvent(event eventParam) error { 654 tokenIDParam, err := event.IndexedFieldUint256("tokenId") 655 if err != nil { 656 return err 657 } 658 659 return eh.dirty.deleteBucketInfo(tokenIDParam.Uint64()) 660 }