github.com/TrueBlocks/trueblocks-core/src/apps/chifra@v0.0.0-20241022031540-b362680128f7/pkg/rpc/contract.go (about)

     1  package rpc
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"sort"
     7  	"sync"
     8  
     9  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base"
    10  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/rpc/query"
    11  	"github.com/ethereum/go-ethereum/common"
    12  )
    13  
    14  var ErrNotAContract = errors.New("not a contract")
    15  
    16  // IsContractAtLatest checks if an account is a contract at the latest block
    17  func (conn *Connection) IsContractAtLatest(address base.Address) error {
    18  	return conn.IsContractAt(address, base.NOPOSN)
    19  }
    20  
    21  // IsContractAt checks if an account is a contract
    22  func (conn *Connection) IsContractAt(address base.Address, bn base.Blknum) error {
    23  	if ec, err := conn.getClient(); err != nil {
    24  		return err
    25  	} else {
    26  		defer ec.Close()
    27  
    28  		ctx := context.Background()
    29  		if code, err := ec.CodeAt(ctx, address.Common(), base.BiFromBn(bn)); err != nil {
    30  			return err
    31  		} else {
    32  			if len(code) == 0 {
    33  				return ErrNotAContract
    34  			}
    35  		}
    36  		return nil
    37  	}
    38  }
    39  
    40  // GetContractCodeAt returns a code (if any) for an address at a block
    41  func (conn *Connection) GetContractCodeAt(addr base.Address, bn base.Blknum) ([]byte, error) {
    42  	if ec, err := conn.getClient(); err != nil {
    43  		return []byte{}, err
    44  	} else {
    45  		defer ec.Close()
    46  		return ec.CodeAt(context.Background(), addr.Common(), base.BiFromBn(bn))
    47  	}
    48  }
    49  
    50  // We check a bunch of different locations for the proxy
    51  var locations = []string{
    52  	"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", // EIP1967
    53  	"0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3", // EIP1967ZOS
    54  	"0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7", // EIP1822
    55  	"0x5f3b5dfeb7b28cdbd7faba78963ee202a494e2a2cc8c9978d5e30d2aebb8c197", // EIP1822ZOS};
    56  	"0x",
    57  }
    58  
    59  // GetContractProxyAt returns the proxy address for a contract if any
    60  func (conn *Connection) GetContractProxyAt(address base.Address, blockNumber base.Blknum) (base.Address, error) {
    61  	if ec, err := conn.getClient(); err != nil {
    62  		return base.Address{}, err
    63  	} else {
    64  		defer ec.Close()
    65  
    66  		method := "eth_call"
    67  		params := query.Params{
    68  			map[string]any{
    69  				"to": address,
    70  				// implementation()
    71  				"data": "0x59679b0f",
    72  			},
    73  			blockNumber,
    74  		}
    75  
    76  		proxyAddr, err := query.Query[string](conn.Chain, method, params)
    77  		var proxy base.Address
    78  		if proxyAddr != nil {
    79  			proxy = base.HexToAddress(*proxyAddr)
    80  		}
    81  		if err == nil && !proxy.IsZero() && proxy.Hex() != address.Hex() {
    82  			return proxy, err
    83  		}
    84  
    85  		for _, location := range locations {
    86  			var value []byte
    87  			value, err = ec.StorageAt(
    88  				context.Background(),
    89  				address.Address,
    90  				common.HexToHash(location),
    91  				base.BiFromBn(blockNumber),
    92  			)
    93  			if err != nil {
    94  				return proxy, err
    95  			}
    96  			proxy = base.BytesToAddress(value)
    97  			if !proxy.IsZero() && proxy.Hex() != address.Hex() {
    98  				err = conn.IsContractAt(proxy, blockNumber)
    99  				if errors.Is(err, ErrNotAContract) {
   100  					// Not a proxy
   101  					return base.Address{}, nil
   102  				}
   103  				return proxy, err
   104  			}
   105  			proxy = base.Address{}
   106  		}
   107  
   108  		return proxy, err
   109  	}
   110  }
   111  
   112  // TODO: We could use a SyncMap here
   113  var deployedCacheMutex sync.Mutex
   114  var deployedCache = make(map[base.Address]base.Blknum)
   115  
   116  func (conn *Connection) GetContractDeployBlock(address base.Address) (block base.Blknum, err error) {
   117  	// TODO: Couldn't we wait here to lock until we need it? Doesn't this lock even when we only read the cache?
   118  	deployedCacheMutex.Lock()
   119  	defer deployedCacheMutex.Unlock()
   120  
   121  	if cached, ok := deployedCache[address]; ok {
   122  		block = cached
   123  		return
   124  	}
   125  
   126  	latest := conn.GetLatestBlockNumber()
   127  	if err = conn.IsContractAt(address, latest); err != nil {
   128  		return
   129  	}
   130  
   131  	found := sort.Search(int(latest)+1, func(bn int) bool {
   132  		err := conn.IsContractAt(address, base.Blknum(bn))
   133  		return err == nil
   134  	})
   135  
   136  	block = base.Blknum(found)
   137  	deployedCache[address] = block
   138  	return
   139  }