-
Notifications
You must be signed in to change notification settings - Fork 492
Add logic for writing signatures to db inside SourcifyDatabaseService
#2357
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
Conversation
36244b2 to
3702f3b
Compare
kuzdogan
left a comment
There was a problem hiding this 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
| signatures.push(getSignatureData(fragment)); | ||
| } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
| 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, |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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), |
There was a problem hiding this comment.
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?
| } catch (error) { | ||
| throw new Error( | ||
| "Failed to extract signatures from ABI due to an invalid fragment", | ||
| ); | ||
| } |
There was a problem hiding this comment.
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
sourcify/services/server/src/server/services/storageServices/SourcifyDatabaseService.ts
Lines 913 to 914 in 6bfbf5f
| const signatureData = extractSignaturesFromAbi(abi); | |
| const signatureColumns = signatureData.map((sig) => ({ |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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:
functionInvalidTypefunction
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
Closes #2317
signature-util.tsto extract signatures from the ABI via ethersstoreSignaturesis also called when a match is updated, because it cannot write any duplicates due to the primary key, nor raise errors due to theON CONFLICTclause