Skip to content

Conversation

@manuelwedler
Copy link
Member

Closes #2317

  • adds a new signature-util.ts to extract signatures from the ABI via ethers
  • writes the signatures to the new table in a separate db transaction, such that verification job will still succeed even though there is an error while writing the signatures
  • storeSignatures is also called when a match is updated, because it cannot write any duplicates due to the primary key, nor raise errors due to the ON CONFLICT clause

Copy link
Member

@kuzdogan kuzdogan left a comment

Choose a reason for hiding this comment

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

Nice and simple. Just a few comments

Comment on lines +19 to +20
signatures.push(getSignatureData(fragment));
}
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 wrapt this in a try/catch because fragment.format() will fail for some library contracts that have an invalid "type" field. Library ABI items are allowed to have custom types because their ABIs are not intended to be publicly callable. However this breaks with ethers when assembling the signature. See the latest messages in the Solidity private chat.

Copy link
Member

Choose a reason for hiding this comment

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

Let's also add a test case for the above issue

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for pointing this out. I made the function throwing in this case now, so we would not store any of the signatures if only one custom type was found. Or was your intention rather to ignore the fragments with custom types?

try {
const { type, verifiedContractId, oldVerifiedContractId } =
await super.insertOrUpdateVerification(verification, poolClient);
await this.insertOrUpdateVerification(verification, poolClient);
Copy link
Member

Choose a reason for hiding this comment

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

Why the change? Does it have an effect on semantics?

Copy link
Member Author

Choose a reason for hiding this comment

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

good catch. super can actually stay. I think it's a leftover from Claude which implemented the feature in a different way at first.

Comment on lines +914 to +922
const signatureColumns = signatureData.map((sig) => ({
signature_hash_32: bytesFromString<BytesKeccak>(sig.signatureHash32),
signature: sig.signature,
signature_type: sig.signatureType,
}));

await this.database.insertSignatures(signatureColumns, poolClient);
await this.database.insertCompiledContractSignatures(
compilationId,
Copy link
Member

Choose a reason for hiding this comment

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

Small thing but we are passing the same signatureColumns to both insertSignatures and insertCompiledContractSignatures functions. Even though the parameter signatures in both functions have different types. This works because the signatureColumns is a union of both types.

I don't have a strong opinion here but just wanted to point out.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, I did this on purpose to keep the code shorter. IMO, when you accept an object typed via an interface, you should also expect the object to possibly have more properties, because TypeScript doesn't guarantee the absence of other properties ("duck typing").

| VerifyFromMetadataInput
| VerifyFromEtherscanInput,
): Promise<void> {
): void {
Copy link
Member

Choose a reason for hiding this comment

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

Good catch, how did you recognize this?

Actually this makes me realize now this function is difficult to read with .then()s. It's ok though.

Copy link
Member Author

Choose a reason for hiding this comment

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

I just read over the function and wondered why the function is async.

We mainly have the then() style here because we need to keep a reference to the promise object. We could move the inner logic to another function to use await, but I think it's fine.

expect(storeSignature).to.exist;

const expectedRetrieveSignatureHash32 = bytesFromString(
keccak256str(retrieveSignature!.signature),
Copy link
Member

Choose a reason for hiding this comment

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

Isn't this self referencing? This should be calculated from teh MockVerificationExport or raw strings above?

Comment on lines 22 to 26
} catch (error) {
throw new Error(
"Failed to extract signatures from ABI due to an invalid fragment",
);
}
Copy link
Member

Choose a reason for hiding this comment

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

This should not throw, or if it does I don't see it being caught anywhere. If there's an invalid type e.g. when it's a library contract ABI we should simply skip. Right now this throws here

const signatureData = extractSignaturesFromAbi(abi);
const signatureColumns = signatureData.map((sig) => ({

Copy link
Member Author

Choose a reason for hiding this comment

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

I still didn't understand precisely how you want to handle this case. If it is a library contract, do you want to skip storing all signatures of the ABI, or do you want to just skip the ABI fragments with custom types and still store the other signatures? Both options seem reasonable to me.

The storeSignatures function actually has a try-catch which is wrapping the lines you referenced here.

Copy link
Member

@kuzdogan kuzdogan Sep 8, 2025

Choose a reason for hiding this comment

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

Hmm I'd say just skip the ABI fragments that are invalid ie. not an "error", "function", or "event". This would also include the constructors. So if a library has valid signatures we will save them.

From my understanding, as is, this code would break at the invalid signature and won't save the rest of the valid signatures. Ie if a contract has signatures of type:

  1. function
  2. InvalidType
  3. function

it will break at 2. and won't save 3., no?

Edit: actually it will break at 2. and won't save any of the signatures. I think we should save the valid ones

Copy link
Member

Choose a reason for hiding this comment

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

We can just silently skip processing if the fragment.type is not one of the valid ones and won't enter it to the getSignatureData which will throw via ethersjs.

Copy link
Member Author

Choose a reason for hiding this comment

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

Alright sounds good to me. I just think we should ignore inside the getSignatureData function and not throw from it in any case then.

@manuelwedler manuelwedler merged commit 3fa5023 into staging Sep 8, 2025
5 checks passed
@github-project-automation github-project-automation bot moved this from Triage to Sprint - Done in Sourcify Public Sep 8, 2025
@manuelwedler manuelwedler deleted the 4bytes-write-logic branch September 8, 2025 14:36
@marcocastignoli marcocastignoli moved this from Sprint - Done to COMPLETED in Sourcify Public Sep 11, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: COMPLETED

Development

Successfully merging this pull request may close these issues.

4bytes: Write logic for the 4bytes in SourcifyDatabase

3 participants