diff --git a/internal/contractStore/contractStore.go b/internal/contractStore/contractStore.go index 7aa59c23..6f403609 100644 --- a/internal/contractStore/contractStore.go +++ b/internal/contractStore/contractStore.go @@ -9,7 +9,6 @@ import ( type ContractStore interface { GetContractForAddress(address string) (*Contract, error) FindOrCreateContract(address string, abiJson string, verified bool, bytecodeHash string, matchingContractAddress string) (*Contract, bool, error) - GetUnverifiedContractForAddress(address string) (*UnverifiedContract, error) FindVerifiedContractWithMatchingBytecodeHash(bytecodeHash string, address string) (*Contract, error) FindOrCreateProxyContract(blockNumber uint64, contractAddress string, proxyContractAddress string) (*ProxyContract, bool, error) GetContractWithProxyContract(address string, atBlockNumber uint64) (*ContractsTree, error) diff --git a/internal/contractStore/pgContractStore/pgContractStore.go b/internal/contractStore/pgContractStore/pgContractStore.go index e3cc3a06..089388bc 100644 --- a/internal/contractStore/pgContractStore/pgContractStore.go +++ b/internal/contractStore/pgContractStore/pgContractStore.go @@ -12,9 +12,8 @@ import ( ) type PgContractStore struct { - Db *gorm.DB - migrated bool - Logger *zap.Logger + Db *gorm.DB + Logger *zap.Logger } func NewPgContractStore(db *gorm.DB, l *zap.Logger) (*PgContractStore, error) { @@ -23,21 +22,9 @@ func NewPgContractStore(db *gorm.DB, l *zap.Logger) (*PgContractStore, error) { Logger: l, } - cs.autoMigrate() - return cs, nil } -func (p *PgContractStore) autoMigrate() { - if p.migrated { - return - } - - // p.Db.AutoMigrate(&contractStore.Contract{}) - - p.migrated = true -} - func (p *PgContractStore) GetContractForAddress(address string) (*contractStore.Contract, error) { var contract *contractStore.Contract @@ -90,21 +77,6 @@ func (p *PgContractStore) FindOrCreateContract( return upsertedContract, found, err } -func (p *PgContractStore) GetUnverifiedContractForAddress(address string) (*contractStore.UnverifiedContract, error) { - var contract *contractStore.UnverifiedContract - - result := p.Db.First(&contract, "contract_address = ?", strings.ToLower(address)) - if result.Error != nil { - if errors.Is(result.Error, gorm.ErrRecordNotFound) { - p.Logger.Sugar().Debugf("Verified contract not found in store '%s'", address) - return nil, nil - } - return nil, result.Error - } - - return contract, nil -} - func (p *PgContractStore) FindVerifiedContractWithMatchingBytecodeHash(bytecodeHash string, address string) (*contractStore.Contract, error) { query := ` select diff --git a/internal/contractStore/sqliteContractStore/sqliteContractStore.go b/internal/contractStore/sqliteContractStore/sqliteContractStore.go new file mode 100644 index 00000000..9ffc59fe --- /dev/null +++ b/internal/contractStore/sqliteContractStore/sqliteContractStore.go @@ -0,0 +1,257 @@ +package sqliteContractStore + +import ( + "errors" + "fmt" + "github.com/Layr-Labs/sidecar/internal/contractStore" + pg "github.com/Layr-Labs/sidecar/internal/postgres" + "github.com/Layr-Labs/sidecar/internal/sqlite" + "go.uber.org/zap" + "gorm.io/gorm" + "gorm.io/gorm/clause" + "strings" +) + +type SqliteContractStore struct { + Db *gorm.DB + Logger *zap.Logger +} + +func NewSqliteContractStore(db *gorm.DB, l *zap.Logger) *SqliteContractStore { + cs := &SqliteContractStore{ + Db: db, + Logger: l, + } + return cs +} + +func (s *SqliteContractStore) GetContractForAddress(address string) (*contractStore.Contract, error) { + var contract *contractStore.Contract + + result := s.Db.First(&contract, "contract_address = ?", address) + if result.Error != nil { + if result.Error == gorm.ErrRecordNotFound { + s.Logger.Sugar().Debugf("Contract not found in store '%s'", address) + return nil, nil + } + return nil, result.Error + } + + return contract, nil +} + +func (s *SqliteContractStore) FindOrCreateContract( + address string, + abiJson string, + verified bool, + bytecodeHash string, + matchingContractAddress string, +) (*contractStore.Contract, bool, error) { + found := false + upsertedContract, err := sqlite.WrapTxAndCommit[*contractStore.Contract](func(tx *gorm.DB) (*contractStore.Contract, error) { + contract := &contractStore.Contract{} + result := s.Db.First(&contract, "contract_address = ?", strings.ToLower(address)) + if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, result.Error + } + + // found contract + if contract.ContractAddress == address && contract.Id != 0 { + found = true + return contract, nil + } + contract = &contractStore.Contract{ + ContractAddress: strings.ToLower(address), + ContractAbi: abiJson, + Verified: verified, + BytecodeHash: bytecodeHash, + MatchingContractAddress: matchingContractAddress, + } + + result = s.Db.Create(contract) + if result.Error != nil { + return nil, result.Error + } + + return contract, nil + }, nil, s.Db) + return upsertedContract, found, err +} + +func (s *SqliteContractStore) indVerifiedContractWithMatchingBytecodeHash(bytecodeHash string, address string) (*contractStore.Contract, error) { + query := ` + select + * + from contracts + where + bytecode_hash = ? + and verified = true + and matching_contract_address = '' + and contract_address != ? + order by id asc + limit 1` + + var contract *contractStore.Contract + result := s.Db.Raw(query, bytecodeHash, address).Scan(&contract) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + s.Logger.Sugar().Debugf("Verified contract not found in store '%s'", bytecodeHash) + return nil, nil + } + return nil, result.Error + } + return contract, nil +} + +func (s *SqliteContractStore) FindOrCreateProxyContract( + blockNumber uint64, + contractAddress string, + proxyContractAddress string, +) (*contractStore.ProxyContract, bool, error) { + found := false + contractAddress = strings.ToLower(contractAddress) + proxyContractAddress = strings.ToLower(proxyContractAddress) + + upsertedContract, err := pg.WrapTxAndCommit[*contractStore.ProxyContract](func(tx *gorm.DB) (*contractStore.ProxyContract, error) { + contract := &contractStore.ProxyContract{} + // Proxy contracts are unique on block_number && contract + result := tx.First(&contract, "contract_address = ? and block_number = ?", contractAddress, blockNumber) + if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, result.Error + } + + // found contract + if contract.ContractAddress == contractAddress { + found = true + return contract, nil + } + proxyContract := &contractStore.ProxyContract{ + BlockNumber: int64(blockNumber), + ContractAddress: contractAddress, + ProxyContractAddress: proxyContractAddress, + } + + result = tx.Model(&contractStore.ProxyContract{}).Clauses(clause.Returning{}).Create(proxyContract) + if result.Error != nil { + return nil, result.Error + } + + return proxyContract, nil + }, nil, s.Db) + return upsertedContract, found, err +} + +func (s *SqliteContractStore) GetContractWithProxyContract(address string, atBlockNumber uint64) (*contractStore.ContractsTree, error) { + address = strings.ToLower(address) + + query := `select + c.contract_address as base_address, + c.contract_abi as base_abi, + pcc.contract_address as base_proxy_address, + pcc.contract_abi as base_proxy_abi, + pcclike.contract_address as base_proxy_like_address, + pcclike.contract_abi as base_proxy_like_abi, + clike.contract_address as base_like_address, + clike.contract_abi as base_like_abi + from contracts as c + left join ( + select + * + from proxy_contracts + where contract_address = ? and block_number <= ? + order by block_number desc limit 1 + ) as pc on (1=1) + left join contracts as pcc on (pcc.contract_address = pc.proxy_contract_address) + left join contracts as pcclike on (pcc.matching_contract_address = pcclike.contract_address) + left join contracts as clike on (c.matching_contract_address = clike.contract_address) + where c.contract_address = ?` + + contractTree := &contractStore.ContractsTree{} + result := s.Db.Raw(query, + address, + atBlockNumber, + address, + ).Scan(&contractTree) + + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + s.Logger.Sugar().Debug(fmt.Sprintf("Contract not found '%s'", address)) + return nil, nil + } + return nil, result.Error + } + if contractTree.BaseAddress == "" { + s.Logger.Sugar().Debug(fmt.Sprintf("Contract not found in store '%s'", address)) + return nil, nil + } + + return contractTree, nil +} + +func (s *SqliteContractStore) SetContractCheckedForProxy(address string) (*contractStore.Contract, error) { + contract := &contractStore.Contract{} + + result := s.Db.Model(contract). + Clauses(clause.Returning{}). + Where("contract_address = ?", strings.ToLower(address)). + Updates(&contractStore.Contract{ + CheckedForProxy: true, + }) + + if result.Error != nil { + return nil, result.Error + } + + return contract, nil +} + +func (s *SqliteContractStore) GetProxyContract(address string) (*contractStore.ProxyContract, error) { + var proxyContract *contractStore.ProxyContract + + result := s.Db.First(&proxyContract, "contract_address = ?", strings.ToLower(address)) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + s.Logger.Sugar().Debugf("Proxy contract not found in store '%s'", address) + return nil, nil + } + return nil, result.Error + } + + return proxyContract, nil +} + +func (s *SqliteContractStore) SetContractAbi(address string, abi string, verified bool) (*contractStore.Contract, error) { + contract := &contractStore.Contract{} + + result := s.Db.Model(contract). + Clauses(clause.Returning{}). + Where("contract_address = ?", strings.ToLower(address)). + Updates(&contractStore.Contract{ + ContractAbi: abi, + Verified: verified, + CheckedForAbi: true, + }) + + if result.Error != nil { + return nil, result.Error + } + + return contract, nil +} + +func (s *SqliteContractStore) SetContractMatchingContractAddress(address string, matchingContractAddress string) (*contractStore.Contract, error) { + contract := &contractStore.Contract{} + + result := s.Db.Model(&contract). + Clauses(clause.Returning{}). + Where("contract_address = ?", strings.ToLower(address)). + Updates(&contractStore.Contract{ + MatchingContractAddress: matchingContractAddress, + }) + + if result.Error != nil { + return nil, result.Error + } + + return contract, nil +} diff --git a/internal/sqlite/sqlite.go b/internal/sqlite/sqlite.go index fc7041e5..f6f98f2b 100644 --- a/internal/sqlite/sqlite.go +++ b/internal/sqlite/sqlite.go @@ -1,6 +1,7 @@ package sqlite import ( + "fmt" "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/logger" @@ -32,3 +33,23 @@ func NewGormSqliteFromSqlite(sqlite gorm.Dialector) (*gorm.DB, error) { } return db, nil } + +func WrapTxAndCommit[T any](fn func(*gorm.DB) (T, error), db *gorm.DB, tx *gorm.DB) (T, error) { + exists := tx != nil + + if !exists { + tx = db.Begin() + } + + res, err := fn(tx) + + if err != nil && !exists { + fmt.Printf("Rollback transaction\n") + tx.Rollback() + } + if err == nil && !exists { + fmt.Printf("Commit transaction\n") + tx.Commit() + } + return res, err +}