Conversation
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
|
||
| /// Canonical order intent. Also the exact bytes hashed (SHA-256) to produce the order UID used in the order PDA's seeds, | ||
| /// and the exact wire format of create_order's `intent` argument. Field order and encoding here are load-bearing: they | ||
| /// must match this program's Rust definition exactly. |
There was a problem hiding this comment.
this comment was added because it probably should have existed in the first place, and not having it triggers an error in the IDL tests.
There was a problem hiding this comment.
Fine to add a comment, but this one is wrong, this is a struct, it doesn't store bytes, and its (Rust) encoding totally isn't the bytes hashed to produce the order UID. If anything, this is EncodedOrderIntent.
fedgiac
left a comment
There was a problem hiding this comment.
One overarching comment: I was never sure what exactly these tests check and what not. Maybe we can add docs to the start of the test file that state clearly what's being covered for each IDL field? This helps us in the future to understand what's needed to improve on the current tests and avoiding duplicated work. It also makes it obvious what a follow-up PR does in the code diff.
Something like this.
Top level:
- address ✔️
- metadata: partial
- docs: ❌
- instructions: partial
- accounts: partial
- events: ✔️ (no events) # and we should actually test this!
- errors: ✔️
- types: ...
- constants: ...
instructions:
- name ✔️
- docs ❌
- discriminator ✔️
- accounts ❌
- args ❌
- return ❌
...
Overall the design makes a lot of sense. It was too complex for the time allotted so I'll need to continue at a later point, but there are quite a bit of comments already.
|
|
||
| /// Canonical order intent. Also the exact bytes hashed (SHA-256) to produce the order UID used in the order PDA's seeds, | ||
| /// and the exact wire format of create_order's `intent` argument. Field order and encoding here are load-bearing: they | ||
| /// must match this program's Rust definition exactly. |
There was a problem hiding this comment.
Fine to add a comment, but this one is wrong, this is a struct, it doesn't store bytes, and its (Rust) encoding totally isn't the bytes hashed to produce the order UID. If anything, this is EncodedOrderIntent.
| fn idl_matches_instruction_discriminators() { | ||
| let idl = idl(); | ||
| for byte in 0u8..=255 { | ||
| if let Ok(ix) = SettlementInstruction::try_from(byte) { |
There was a problem hiding this comment.
Probably at this point it wouldn't be that bad to create a function similar to parse_instruction where we populate each builder with placeholder data. This is super helpful because then we can check everything in an instruction automatically (number of accounts, order, whether it's signer/writable), the discriminator comes for free.
Also, we're going to remember to add a new function because we need to add a new variant to compile.
There was a problem hiding this comment.
didn't this actually get added by the backend team? not sure.
There was a problem hiding this comment.
Right, this is exactly fn build(instruction: SettlementInstruction) -> Instruction, we can use that for testing.
| /// Translates a Rust field type into the IDL spec's type grammar, so field | ||
| /// types can be compared as JSON. Panics on anything the program's data types | ||
| /// don't currently use. |
There was a problem hiding this comment.
It would be much nicer if the Rust fields were converted to a struct with all relevant content, the same for the fields in the JSON, and then the two fields were compared with each other. This should give a clearer diff and overall be more flexible.
There was a problem hiding this comment.
yea ok that makes sense, but how does that have to do with the specific segment of code you highlighted? are you suggesting we should focus on stringifying here instead of constructing a json! type?
| let syn::Expr::Lit(syn::ExprLit { | ||
| lit: syn::Lit::Int(len), | ||
| .. | ||
| }) = &array.len | ||
| else { | ||
| panic!("{context}: array length must be an integer literal"); | ||
| }; | ||
| let len: u64 = len.base10_parse().expect("array length must be a u64"); |
There was a problem hiding this comment.
This was dark magic to me. I'd suggest isolating this into a function get_array_length or something.
Co-authored-by: Federico Giacon <58218759+fedgiac@users.noreply.github.com>
Co-authored-by: Federico Giacon <58218759+fedgiac@users.noreply.github.com>
* add comments for settlement instruction and validate match * simplify superfluous comments in the IDL in general * switch to using `LazyLock` and update call sites
| fn idl_matches_instruction_discriminators() { | ||
| let idl = idl(); | ||
| for byte in 0u8..=255 { | ||
| if let Ok(ix) = SettlementInstruction::try_from(byte) { |
There was a problem hiding this comment.
Right, this is exactly fn build(instruction: SettlementInstruction) -> Instruction, we can use that for testing.
| /// Pulls funds for a batch of orders. Must be paired in the same | ||
| /// transaction with a `FinalizeSettle` at `finalize_ix_index`. | ||
| BeginSettle = 0, | ||
| /// Validates that a `BeginSettle` at `begin_ix_index` exists and points | ||
| /// back at this instruction. Must not be called via CPI. | ||
| FinalizeSettle = 1, | ||
| /// Allocates a per-order PDA and writes the initial `OrderAccount` body. | ||
| CreateOrder = 2, | ||
| /// Creates the singleton settlement state PDA. Succeeds only once. | ||
| Initialize = 3, | ||
| /// Creates one or more per-token buffer PDAs (SPL token accounts) in a | ||
| /// single instruction. | ||
| /// | ||
| /// Each buffer_pda_i must be the canonical PDA for seeds | ||
| /// [SETTLEMENT_SEED, mint_i, "buffer"]. | ||
| CreateBuffer = 4, | ||
| /// Closes an expired order PDA and returns its rent lamports to the | ||
| /// created_by account recorded in the order body. The instruction may only | ||
| /// be executed after the order's valid_to timestamp has elapsed. | ||
| /// | ||
| /// No signature requirement: anyone may reclaim an expired order on behalf | ||
| /// of its reclaim_recipient. |
There was a problem hiding this comment.
Nit: I don't think any of these descriptions says something that someone reading this for the first time should be reading.
What I'd expect:
- Begin/Finalize: they process user orders, one takes funds from the user, the other sends funds to the user, respecting limit prices.
- CreateOrder: lets an owner create an order for the protocol.
- Initialize: determines the initial parameters of the protocol, like the authorities.
- CreateBuffer: ok but the second line is waaay too specific compared to everything else.
- ReclaimOrder: fine-ish but I wouldn't use variable names, rather an actual name describing what happens, like "after order expiration."
| /// Since each IDL section follows the same pattern where each section is an array of objects which contain a field `name`, this function | ||
| /// is useful for finding just about any item we need in the IDL file. |
There was a problem hiding this comment.
Very nitty but line length is weird. The comment itself is very helpful.
| if let Some(Value::Array(bytes)) = map.get("value") { | ||
| let decoded: Vec<u8> = bytes | ||
| .iter() | ||
| .map(|b| b.as_u64().expect("seed byte must be a number") as u8) | ||
| .collect(); |
There was a problem hiding this comment.
Repeating code from discriminator. In particular, this code introduces a bug by using as unlike the code from the other function. Maybe we should just introduce decode_byte_array and use that in both functions. (Maybe discriminator isn't needed anymore then.)
| out | ||
| } | ||
|
|
||
| fn confirm_idl_match(idl_section: Section, idl_name: &str, discriminator_byte: u8) { |
There was a problem hiding this comment.
Nit: a comment should specify that this is only for accounts and instructions, it sounds too generic given its name. Also the match part is too generic, there are quite a few fields not being checked. None of this is really a problem per se, it's just that the name of the function doesn't show it.
| } | ||
|
|
||
| #[test] | ||
| fn idl_matches_account_discriminators() { |
There was a problem hiding this comment.
Suggested extra test: this test is exhaustive, that is, there are no accounts with a discriminator of more than 1 byte. Probably the same applies to instructions.
it increases the amount of code overall, but it puts us in the right trajectory to be effectively generating parts of the IDL from rust.
fedgiac
left a comment
There was a problem hiding this comment.
A lot of things are going on and it's overall complex logic to review; I think there are a few places where a redesign could help.
However, there's a strong pressure to have the IDL ready and this is progress.
I only have a shallow understanding of the current code but since it doesn't affect the rest of the program, I think we can merge.
None of my comments prevent merging.
| /// [...]}`, with the fields in declaration order, which is the order they're | ||
| /// laid out on the wire. |
There was a problem hiding this comment.
I never intentionally sorted structs based on that, is this really true?
There was a problem hiding this comment.
I'm pretty sure in the past in comments you have complained when the order is not consistent with how it actually is (ex. when Flags was introduced, tried to but couldn't find original comment). Maybe it wasn't intentional, but it seems thats how it is.
And since this is something that would now be implied since we use it for the IDL, it seems we should be explicitly explaining this in DESIGN.md or similar just to ensure we keep following the same pattern.
There was a problem hiding this comment.
Yeah, I remember the comment specifically for Flags. If it's the case for all the other structs, that's great, it's a good thing to have. Sounds good adding a short comment in the design file.
| /// A per-token buffer PDA. The IDL can only declare the guaranteed index-0 | ||
| /// buffer of the unbounded run an instruction actually accepts, so the mint it | ||
| /// derives from is `mint_0`. | ||
| const BUFFER_PDA_0: &[Seed] = &[ | ||
| Seed::Const(SETTLEMENT_SEED), | ||
| Seed::Account("mint_0"), | ||
| Seed::Const(BUFFER_SEED), | ||
| ]; |
There was a problem hiding this comment.
Where is the string mint_0 coming from? If we use a string referenced from the IDL, the seeds cannot be a constant.
There was a problem hiding this comment.
its coming from the account spec of CreateBuffers which accepts a list of mints and buffers pdas to create buffers for. So we effectively define mint_0 as the first mint and hope the fact that the 0 is there and the docs will key IDL users into using additional buffers specification if they would like
There was a problem hiding this comment.
My main issue with this is that "name": "mint_0" is added by hand to the IDL and it's weird that the code references directly a parameter name (mint_0) that could not exist or be changed at any time without anything else in the test being aware about it. Not sure how to easily resolve that however, let's keep it like this.
Co-authored-by: Federico Giacon <58218759+fedgiac@users.noreply.github.com>
Co-authored-by: Federico Giacon <58218759+fedgiac@users.noreply.github.com>
fedgiac
left a comment
There was a problem hiding this comment.
Same as in the previous review. All comments but the two open ones have been addressed or have been dismissed, fine to merge.
…73) # Description Using the IDL from #65 , generate a TS/JS client for the settlement program, and write a test to verify the critical path `createOrder` instruction is convenient and correct to use. ## Changes and Rationale The JS/TS client was selected because the backend has an immediate dependency on it. A rust library can also be generated with codama which would allow for us to have E2E IDL library generation tests, but this has been left as an issue for another time. Codama is also a native Node.JS application, so javascript dependencies were going to come one way or another. The library is generated through a script stored alongside the IDL, `generate.mjs`, which invokes codama. It also includes certain overrides and customized settings It is important that clients are able to call `createOrder` without having to make any unnecessary changes, but the IDL cannot fully express the grammar for the `createOrder` instruction due to the `orderPda` depending upon a computed hash. Thankfully, [codama provides `resolverValueNode`](https://github.com/codama-idl/codama/blob/main/packages/node-types/src/generated/contextualValueNodes/ResolverValueNode.ts#L5), which allows for defining a custom function in the destination language that can be hooked in to provide this missing functionality. It requires adding a `hooks.ts` file in the source directory which codama hardcodes to read. The flags for an order cannot be expressed in IDL directly either, so a helper file was added for this. PNPM is used as the package manager. This [matches](https://github.com/cowprotocol/cowswap/blob/develop/pnpm-lock.yaml) the package manager used by the frontend. The correct version is locked and the Justfile commands have it installed through `corepack`, a utility that is shipped with modern versions of Node.JS. The single typescript test was added to the CI as a new job. ## Out of scope This is not intended to be a comprehensive test of the IDL generation or all the instructions exported by the library. Its just to give us a starting point to make sure that the IDL generation and the corresponding library it generates works as expected. Getting every single instruction that is not currently IDL compatible at all (such as `BeginSettle` and `FinalizeSettle`) is a much more complicated task and also has been left out. Releasing the generated package to npm registry or elsewhere for consumption is also kept outside the scope of this PR. ## How to Test/Use Node.js is now a dependency of the repository for a full build. Node.js can be installed many ways, but I recommend [NVM](https://github.com/nvm-sh/nvm). I used node.js 24 for my testing and the CI. 1. Check out this branch 2. Use `just generate-js-client` to create the generated files 3. Use `just test-js-client` to run the one JS lib test. --------- Co-authored-by: Federico Giacon <58218759+fedgiac@users.noreply.github.com> Co-authored-by: Denis Makarov <limitofzero@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Description
Generate a Solana IDL using AI, and validate its baseline correctness using smoke tests.
Summary
programs/settlement/idl/cow_settlement.json) describing the settlement program's instructions/accounts/types, for IDL-driven tooling (e.g. Solscan). This program is native/Pinocchio, not Anchor, so there's no generated IDL to start from.docsfields instead:BeginSettle's dynamically-shaped tail (order count / bumps / transfer counts / pull amounts) has no Borsh-expressible layout (no length prefixes, and a trailing array whose length is the sum of an earlier array).order_pda's PDA seed issha256(intent_bytes)— a hash of the whole instruction argument, not a plain field/account reference the PDA-seed grammar can point at.create_buffer's account list only includes the first buffer account, since its not possible to specify more than one account as an array specified. Additional buffer accounts must be specified manually.How to review this PR
The IDL file is quite long. To minimize the amount of excess effort needed, after only a quick review/skim of the IDL file itself, check out the tests and see what properties are checked/validated.
Summary of coverage
The tests are primarily focused on identifying drift rather than fundamental correctness.
For fundamental correctness, it is expected that further tests will be included in #73 , as its much easier to test the functional outputs of the IDL (for example, the TS library) for successful encoding behavior.
Summary of what is covered and what is not:
Covered
File-level
address == declare_id!;metadata.version == CARGO_PKG_VERSIONCross-checked against Rust source (via
syn)OrderIntent,OrderAccount,StateAccount -> SettlementState— docs, field name/order/typeOrderKind,Role— docs, variant names in order, and any Rust variant pinning = N must agree with its index (the only thing the spec can express about a variant's wire value)create_orderhandled explicitly, 3 asserted arg-free. New instructions will need to have the appropriate check added manually here.interface::pdaconstant;SETTLEMENT_SEEDandBUFFER_SEEDeach appearNot covered
How to test
The smoke tests are run alongside the existing
testscrate suite, so you can runjust testto verify the tests.Manually inspect the IDL file itself, especially the instructions and how they were translated. Comment on anything unusual or bad comments.
Check out #73 , which this PR is stacked upon, to see the IDL being used to generate a Javascript library. This Javascript library has its own tests verifying that the program can be interacted with on a LiteSVM instance!
New Dependencies!
Stacked on by #73
fixes kaze/sc-255-write-idl-and-generate-corresponding-libraries-for
🤖 Generated with Claude Code