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  }