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 }