github.com/amazechain/amc@v0.1.3/contracts/deposit/contract.go (about)

     1  // Copyright 2023 The AmazeChain Authors
     2  // This file is part of the AmazeChain library.
     3  //
     4  // The AmazeChain library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The AmazeChain library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the AmazeChain library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package deposit
    18  
    19  import (
    20  	"context"
    21  	"github.com/amazechain/amc/common"
    22  	"github.com/amazechain/amc/common/crypto/bls"
    23  	"github.com/amazechain/amc/common/hexutil"
    24  	"github.com/amazechain/amc/common/transaction"
    25  	"github.com/amazechain/amc/common/types"
    26  	"github.com/amazechain/amc/log"
    27  	event "github.com/amazechain/amc/modules/event/v2"
    28  	"github.com/amazechain/amc/modules/rawdb"
    29  	"github.com/amazechain/amc/params"
    30  	"github.com/holiman/uint256"
    31  	"github.com/ledgerwatch/erigon-lib/kv"
    32  	"sync"
    33  )
    34  
    35  const (
    36  	//
    37  	DayPerMonth = 30
    38  	//
    39  	fiftyDeposit       = 50
    40  	OneHundredDeposit  = 100
    41  	FiveHundredDeposit = 500
    42  
    43  	fuji2000Deposit = 2000
    44  	fuji800Deposit  = 800
    45  	fuji200Deposit  = 200
    46  	//
    47  	fiftyDepositMaxTaskPerEpoch       = 500
    48  	OneHundredDepositMaxTaskPerEpoch  = 100
    49  	FiveHundredDepositMaxTaskPerEpoch = 100
    50  	//
    51  	fujiMaxTaskPerEpoch = 50
    52  	//
    53  	fiftyDepositRewardPerMonth       = 0.375 * params.AMT
    54  	OneHundredDepositRewardPerMonth  = 1 * params.AMT
    55  	FiveHundredDepositRewardPerMonth = 6.25 * params.AMT //max uint64 = ^uint64(0) ≈ 18.44 AMT so 15 AMT is ok
    56  	//
    57  	fuji200RewardPerEpoch  = 0.025 * params.AMT
    58  	fuji800RewardPerEpoch  = 0.1 * params.AMT
    59  	fuji2000RewardPerMonth = 10 * params.AMT
    60  )
    61  
    62  // DepositContract d
    63  type DepositContract interface {
    64  	WithdrawnSignature() types.Hash
    65  	DepositSignature() types.Hash
    66  	UnpackDepositLogData(data []byte) (publicKey []byte, signature []byte, depositAmount *uint256.Int, err error)
    67  	IsDepositAction(sig [4]byte) bool
    68  }
    69  
    70  func GetDepositInfo(tx kv.Tx, addr types.Address) *Info {
    71  
    72  	pubkey, depositAmount, err := rawdb.GetDeposit(tx, addr)
    73  	if err != nil {
    74  		return nil
    75  	}
    76  
    77  	var (
    78  		maxRewardPerEpoch *uint256.Int
    79  		rewardPerBlock    *uint256.Int
    80  	)
    81  	depositEther := new(uint256.Int).Div(depositAmount, uint256.NewInt(params.AMT)).Uint64()
    82  	switch depositEther {
    83  	case fiftyDeposit:
    84  		rewardPerBlock = new(uint256.Int).Div(uint256.NewInt(fiftyDepositRewardPerMonth), uint256.NewInt(DayPerMonth*fiftyDepositMaxTaskPerEpoch))
    85  		maxRewardPerEpoch = new(uint256.Int).Mul(rewardPerBlock, uint256.NewInt(fiftyDepositMaxTaskPerEpoch))
    86  	case OneHundredDeposit:
    87  		rewardPerBlock = new(uint256.Int).Div(uint256.NewInt(OneHundredDepositRewardPerMonth), uint256.NewInt(DayPerMonth*OneHundredDepositMaxTaskPerEpoch))
    88  		rewardPerBlock = new(uint256.Int).Add(rewardPerBlock, uint256.NewInt(params.Wei))
    89  		maxRewardPerEpoch = new(uint256.Int).Mul(rewardPerBlock, uint256.NewInt(OneHundredDepositMaxTaskPerEpoch))
    90  	case FiveHundredDeposit:
    91  		rewardPerBlock = new(uint256.Int).Div(uint256.NewInt(FiveHundredDepositRewardPerMonth), uint256.NewInt(DayPerMonth*FiveHundredDepositMaxTaskPerEpoch))
    92  		rewardPerBlock = new(uint256.Int).Add(rewardPerBlock, uint256.NewInt(params.Wei))
    93  		//
    94  		maxRewardPerEpoch = new(uint256.Int).Mul(rewardPerBlock, uint256.NewInt(FiveHundredDepositMaxTaskPerEpoch))
    95  	case fuji200Deposit:
    96  		rewardPerBlock = new(uint256.Int).Div(uint256.NewInt(fuji200RewardPerEpoch), uint256.NewInt(fujiMaxTaskPerEpoch))
    97  		maxRewardPerEpoch = new(uint256.Int).SetUint64(fuji200RewardPerEpoch)
    98  	case fuji800Deposit:
    99  		rewardPerBlock = new(uint256.Int).Div(uint256.NewInt(fuji800RewardPerEpoch), uint256.NewInt(fujiMaxTaskPerEpoch))
   100  		maxRewardPerEpoch = new(uint256.Int).SetUint64(fuji800RewardPerEpoch)
   101  	case fuji2000Deposit:
   102  		rewardPerBlock = new(uint256.Int).Div(uint256.NewInt(fuji2000RewardPerMonth), uint256.NewInt(DayPerMonth*fujiMaxTaskPerEpoch))
   103  		rewardPerBlock = new(uint256.Int).Add(rewardPerBlock, uint256.NewInt(params.Wei))
   104  
   105  		maxRewardPerEpoch = new(uint256.Int).Mul(rewardPerBlock, uint256.NewInt(fujiMaxTaskPerEpoch))
   106  	case 10: //todo
   107  		return nil
   108  	default:
   109  		panic("wrong deposit amount")
   110  	}
   111  
   112  	return &Info{
   113  		pubkey,
   114  		depositAmount,
   115  		rewardPerBlock,
   116  		maxRewardPerEpoch,
   117  	}
   118  }
   119  
   120  type Info struct {
   121  	PublicKey         types.PublicKey `json:"PublicKey"`
   122  	DepositAmount     *uint256.Int    `json:"DepositAmount"`
   123  	RewardPerBlock    *uint256.Int    `json:"RewardPerBlock"`
   124  	MaxRewardPerEpoch *uint256.Int    `json:"MaxRewardPerEpoch"`
   125  }
   126  
   127  //func NewInfo(depositAmount uint256.Int, publicKey bls.PublicKey) *Info {
   128  //	return &Info{
   129  //		PublicKey:     publicKey,
   130  //		DepositAmount: depositAmount,
   131  //	}
   132  //}
   133  
   134  type Deposit struct {
   135  	ctx        context.Context
   136  	cancel     context.CancelFunc
   137  	wg         sync.WaitGroup
   138  	blockChain common.IBlockChain
   139  	db         kv.RwDB
   140  
   141  	logsSub   event.Subscription // Subscription for new log event
   142  	rmLogsSub event.Subscription // Subscription for removed log event
   143  
   144  	logsCh   chan common.NewLogsEvent     // Channel to receive new log event
   145  	rmLogsCh chan common.RemovedLogsEvent // Channel to receive removed log event
   146  
   147  	depositContracts map[types.Address]DepositContract
   148  }
   149  
   150  func NewDeposit(ctx context.Context, bc common.IBlockChain, db kv.RwDB, depositContracts map[types.Address]DepositContract) *Deposit {
   151  	c, cancel := context.WithCancel(ctx)
   152  	d := &Deposit{
   153  		ctx:              c,
   154  		cancel:           cancel,
   155  		blockChain:       bc,
   156  		db:               db,
   157  		logsCh:           make(chan common.NewLogsEvent),
   158  		rmLogsCh:         make(chan common.RemovedLogsEvent),
   159  		depositContracts: depositContracts,
   160  	}
   161  
   162  	d.logsSub = event.GlobalEvent.Subscribe(d.logsCh)
   163  	d.rmLogsSub = event.GlobalEvent.Subscribe(d.rmLogsCh)
   164  
   165  	if d.logsSub == nil || d.rmLogsSub == nil {
   166  		log.Error("Subscribe for event system failed")
   167  	}
   168  	return d
   169  }
   170  
   171  func (d *Deposit) Start() {
   172  	d.wg.Add(1)
   173  	go d.eventLoop()
   174  }
   175  
   176  func (d *Deposit) Stop() error {
   177  	d.cancel()
   178  	d.wg.Wait()
   179  	return nil
   180  }
   181  
   182  func (d *Deposit) IsDepositAction(txs *transaction.Transaction) bool {
   183  	var (
   184  		depositContract      DepositContract
   185  		foundDepositContract bool
   186  	)
   187  	to := txs.To()
   188  	if to == nil {
   189  		return false
   190  	}
   191  	if depositContract, foundDepositContract = d.depositContracts[*to]; !foundDepositContract {
   192  		return false
   193  	}
   194  
   195  	if len(txs.Data()) < 4 {
   196  		return false
   197  	}
   198  
   199  	var sig [4]byte
   200  	copy(sig[:], txs.Data()[:4])
   201  	if !depositContract.IsDepositAction(sig) {
   202  		return false
   203  	}
   204  
   205  	return true
   206  }
   207  
   208  func (d *Deposit) eventLoop() {
   209  	// Ensure all subscriptions get cleaned up
   210  	defer func() {
   211  		d.logsSub.Unsubscribe()
   212  		d.rmLogsSub.Unsubscribe()
   213  		d.wg.Done()
   214  		log.Info("Context closed, exiting goroutine (eventLoop)")
   215  	}()
   216  
   217  	for {
   218  		select {
   219  		case logEvent := <-d.logsCh:
   220  			for _, l := range logEvent.Logs {
   221  				if depositContract, found := d.depositContracts[l.Address]; found {
   222  					if l.Topics[0] == depositContract.DepositSignature() {
   223  						d.handleDepositEvent(l.TxHash, l.Data, depositContract)
   224  					} else if l.Topics[0] == depositContract.WithdrawnSignature() {
   225  						d.handleWithdrawnEvent(l.TxHash, l.Data)
   226  					}
   227  				}
   228  			}
   229  		case logRemovedEvent := <-d.rmLogsCh:
   230  			for _, l := range logRemovedEvent.Logs {
   231  				log.Info("logEvent", "address", l.Address, "data", l.Data, "")
   232  			}
   233  		case <-d.logsSub.Err():
   234  			return
   235  		case <-d.rmLogsSub.Err():
   236  			return
   237  		case <-d.ctx.Done():
   238  			return
   239  		}
   240  	}
   241  }
   242  
   243  func (d *Deposit) handleDepositEvent(txHash types.Hash, data []byte, depositContract DepositContract) {
   244  	// 1
   245  	pb, sig, amount, err := depositContract.UnpackDepositLogData(data)
   246  	if err != nil {
   247  		log.Warn("cannot unpack deposit log data", "err", err)
   248  		return
   249  	}
   250  	// 2
   251  	signature, err := bls.SignatureFromBytes(sig)
   252  	if err != nil {
   253  		log.Warn("cannot unpack BLS signature", "signature", hexutil.Encode(sig), "err", err)
   254  		return
   255  	}
   256  	// 3
   257  	publicKey, err := bls.PublicKeyFromBytes(pb)
   258  	if err != nil {
   259  		log.Warn("cannot unpack BLS publicKey", "publicKey", hexutil.Encode(pb), "err", err)
   260  		return
   261  	}
   262  	// 4
   263  	log.Trace("DepositEvent verify:", "signature", hexutil.Encode(signature.Marshal()), "publicKey", hexutil.Encode(publicKey.Marshal()), "msg", hexutil.Encode(amount.Bytes()))
   264  	if signature.Verify(publicKey, amount.Bytes()) {
   265  		var tx *transaction.Transaction
   266  		rwTx, err := d.db.BeginRw(d.ctx)
   267  		defer rwTx.Rollback()
   268  		if err != nil {
   269  			log.Error("cannot open db", "err", err)
   270  			return
   271  		}
   272  
   273  		tx, _, _, _, err = rawdb.ReadTransactionByHash(rwTx, txHash)
   274  		if err != nil {
   275  			log.Error("rawdb.ReadTransactionByHash", "err", err, "hash", txHash)
   276  		}
   277  
   278  		if tx != nil {
   279  			log.Info("add Deposit info", "address", tx.From(), "amount", amount.String())
   280  
   281  			var pub types.PublicKey
   282  			pub.SetBytes(publicKey.Marshal())
   283  			//
   284  			rawdb.PutDeposit(rwTx, *tx.From(), pub, *amount)
   285  			rwTx.Commit()
   286  		}
   287  	} else {
   288  		log.Error("DepositEvent cannot Verify signature", "signature", hexutil.Encode(sig), "publicKey", hexutil.Encode(pb), "message", hexutil.Encode(amount.Bytes()), "err", err)
   289  	}
   290  }
   291  
   292  func (d *Deposit) handleWithdrawnEvent(txHash types.Hash, data []byte) {
   293  	var tx *transaction.Transaction
   294  
   295  	rwTx, err := d.db.BeginRw(d.ctx)
   296  	defer rwTx.Rollback()
   297  	if err != nil {
   298  		log.Error("cannot open db", "err", err)
   299  		return
   300  	}
   301  	tx, _, _, _, err = rawdb.ReadTransactionByHash(rwTx, txHash)
   302  	if err != nil {
   303  		log.Error("rawdb.ReadTransactionByHash", "err", err, "hash", txHash)
   304  		return
   305  	}
   306  	if tx == nil {
   307  		log.Error("cannot find Transaction", "err", err, "hash", txHash)
   308  		return
   309  	}
   310  
   311  	err = rawdb.DeleteDeposit(rwTx, *tx.From())
   312  	if err != nil {
   313  		log.Error("cannot delete deposit", "err", err)
   314  		return
   315  	}
   316  	rwTx.Commit()
   317  }