Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(sidecar-extensibility): detect upgradeable contracts #237

Open
wants to merge 9 commits into
base: master
Choose a base branch
from

Conversation

serichoi65
Copy link

@serichoi65 serichoi65 commented Feb 11, 2025

[WIP with PR description]

This PR includes two features:

  1. Detecting an upgradeable contract when we process transaction logs
  2. Pulling ABIs from IPFS using the bytecode

References: #223

@serichoi65 serichoi65 requested a review from a team as a code owner February 11, 2025 17:43
@serichoi65 serichoi65 marked this pull request as draft February 11, 2025 17:43
@serichoi65 serichoi65 marked this pull request as ready for review February 13, 2025 18:34
@serichoi65 serichoi65 changed the title detect upgradeable contracts feat(sidecar-extensibility): detect upgradeable contracts Feb 13, 2025
@serichoi65 serichoi65 force-pushed the seri.choi/upgradable-contracts branch from 2b3b4c7 to c05e33b Compare February 13, 2025 19:20
@serichoi65 serichoi65 force-pushed the seri.choi/upgradable-contracts branch from c05e33b to d91781e Compare February 13, 2025 19:23
zap.String("proxyContractAddress", proxyContractAddress),
)
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd simplify this a bit:

if err != nil {
    cm.Logger.Sugar().Errorw("Failed to create proxy contract",
      zap.Error(err),
      zap.String("contractAddress", contractAddress),
      zap.String("proxyContractAddress", proxyContractAddress),
    )
    return nil, err
}
if found {
  cm.Logger.Sugar().Debugw("Found existing proxy contract",
    zap.String("contractAddress", contractAddress),
    zap.String("proxyContractAddress", proxyContractAddress),
  )
  return proxyContract, nil
}

cm.Logger.Sugar().Debugw("Created proxy contract",
  zap.String("contractAddress", contractAddress),
  zap.String("proxyContractAddress", proxyContractAddress),
)

That said, I would actually avoid using theFindOrCreateProxyContract at all and break this into two calls:

  1. Get the proxy contract by implementation address and its proxy. If it exists, return it
  2. Explicitly create it once you have all of the data to do so

In blocklake we did it the way you have it here because we had the ability to backfill and retry. In the sidecar, we dont have that luxury, so rather than upsert the proxy with only half the data, then update it with more info, but then potentially deal with having to purge it later since it can only half exist, we want to make a single atomic insert.

}
}

bytecode, err := cm.EthereumClient.GetCode(context.Background(), proxyContractAddress)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we need to take a context object, we should have CreateProxyContract take it as an argument and pass it along

zap.String("bytecodeHash", bytecodeHash),
)

_, _, err = cm.ContractStore.FindOrCreateContract(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like I said above, we should refactor this whole create function to create the entire proxy contract in one call rather than upserting it, fetching data, then updating it.


func (f *Fetcher) GetContractStorageSlot(ctx context.Context, contractAddress string, blockNumber uint64) (string, error) {
stringBlock := ""
if blockNumber == 0 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we process everything block by block, we want this to explicitly be the block height we're processing, i.e. the provided blockHeight argument

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's 0, we should return an error

@@ -156,6 +157,55 @@ func (idx *Indexer) ParseInterestingTransactionsAndLogs(ctx context.Context, fet
return parsedTransactions, nil
}

func (idx *Indexer) IndexContractUpgrade(ctx context.Context, blockNumber uint64, upgradedLog *parser.DecodedLog) error {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than this living in the Indexer this should probably be in the contract manager and be called something like HandleContractUpgrade

t.Fatal(err)
}

baseUrl := "https://winter-white-crater.ethereum-holesky.quiknode.pro/1b1d75c4ada73b7ad98e1488880649d4ea637733/"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably make this a node that isnt one of our paid quiknode endpoints


t.Run("Test indexing contract upgrades", func(t *testing.T) {
// Create a contract
_, found, err := contractStore.FindOrCreateContract(contract.ContractAddress, contract.ContractAbi, contract.Verified, contract.BytecodeHash, contract.MatchingContractAddress, false)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an okay way to do this. IMO I would create the models directly in the db with grm.Model().Create(), main reason being it saves you (and anyone else) from needing to update this later if FindOrCreateContract changes for some reason.

{
Name: "implementation",
Type: "address",
Value: common.HexToAddress("0x789"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would recommend using a valid address here just to make it as complete as possible

res = grm.Raw(`select count(*) from proxy_contracts where contract_address=@contractAddress`, sql.Named("contractAddress", contractAddress)).Scan(&proxyContractCount)
assert.Nil(t, res.Error)
assert.Equal(t, 2, proxyContractCount)
})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably need a test as well to cover getting the address from the storage slot

pkg/pipeline/pipeline.go Outdated Show resolved Hide resolved
@non-fungible-nelson
Copy link
Collaborator

Hi seri - great PR! Can we have a description please?

@seanmcgary
Copy link
Member

@serichoi65 I'll let you fill in the PR body with implementation specifics.

@non-fungible-nelson I added a link to the related github issue this addresses

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants