├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── contracts ├── Makefile ├── Scarb.lock ├── Scarb.toml ├── abi │ └── simple_get_set.json └── src │ ├── basic.cairo │ ├── event.cairo │ ├── gen.cairo │ ├── lib.cairo │ └── simple_get_set.cairo ├── crates ├── macros │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── abigen │ │ ├── contract_abi.rs │ │ ├── expand │ │ │ ├── contract.rs │ │ │ ├── enum.rs │ │ │ ├── event.rs │ │ │ ├── function.rs │ │ │ ├── generic.rs │ │ │ ├── mod.rs │ │ │ ├── struct.rs │ │ │ └── utils.rs │ │ └── mod.rs │ │ └── lib.rs └── parser │ ├── Cargo.toml │ └── src │ ├── abi_types │ ├── array.rs │ ├── basic.rs │ ├── generic.rs │ ├── mod.rs │ └── tuple.rs │ ├── cairo_enum.rs │ ├── cairo_event.rs │ ├── cairo_function.rs │ ├── cairo_struct.rs │ ├── cairo_types │ ├── error.rs │ ├── mod.rs │ └── types │ │ ├── array.rs │ │ ├── boolean.rs │ │ ├── felt.rs │ │ ├── integers.rs │ │ ├── mod.rs │ │ ├── option.rs │ │ ├── result.rs │ │ ├── starknet.rs │ │ └── tuple.rs │ └── lib.rs ├── examples └── simple_get_set.rs ├── scripts └── clippy.sh ├── src └── lib.rs └── test.abi.json /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Cargo Build & Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | cargo_test: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | - uses: dtolnay/rust-toolchain@stable 18 | - run: cargo test --workspace 19 | 20 | cargo_format_and_clippy: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v3 24 | - uses: dtolnay/rust-toolchain@stable 25 | with: 26 | components: rustfmt, clippy 27 | - run: cargo fmt 28 | - run: cargo clippy --all-targets -- -D warnings 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | */target -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "starknet-abigen" 3 | version = "0.1.2" 4 | edition = "2021" 5 | 6 | [workspace] 7 | members = [ 8 | "crates/macros", 9 | "crates/parser", 10 | ] 11 | 12 | [workspace.dependencies] 13 | # workspace crates 14 | starknet-abigen-parser = { path = "crates/parser" } 15 | starknet-abigen-macros = { path = "crates/macros" } 16 | 17 | # serde 18 | serde = { version = "1.0", default-features = false, features = ["alloc"] } 19 | serde_json = { version = "1.0", default-features = false, features = ["alloc"] } 20 | 21 | starknet = "0.7.0" 22 | thiserror = "1.0" 23 | anyhow = "1.0" 24 | tokio = { version = "1.15.0", features = ["full"] } 25 | url = "2.2.2" 26 | 27 | # Dependencies for the testing app in src. 28 | [dependencies] 29 | url.workspace = true 30 | anyhow.workspace = true 31 | tokio.workspace = true 32 | starknet.workspace = true 33 | serde_json.workspace = true 34 | 35 | # Local 36 | starknet-abigen-parser.workspace = true 37 | starknet-abigen-macros.workspace = true 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2023 Tarrence van As 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ⚠️ ARCHIVED ⚠️ 2 | This repo will no longer be maintained, in favor of it's new version available here: https://github.com/cartridge-gg/cainome. 3 | 4 | # Starknet abigen for rust bindings 5 | 6 | Passionate work about providing rust binding to Starknet community, by the community. 7 | 8 | The current state of the repo is still very experimental, but we are 9 | reaching a first good milestrone. 10 | 11 | - [X] Types generation with serialization/deserialization for any type in the contract. 12 | - [X] Support for generic types. 13 | - [X] Auto generation of the contract with it's functions (call and invoke). 14 | - [X] Generation of Events structs to parse automatically `EmittedEvent`. 15 | 16 | ## How to use it 17 | 18 | For now this crate is not yet published on `crated.io`, but here is how you can do the following. 19 | 20 | ```toml 21 | # Cargo.toml of your project 22 | 23 | [dependencies] 24 | starknet-abigen-parser = { git = "https://github.com/glihm/starknet-abigen-rs", tag = "v0.1.3" } 25 | starknet-abigen-macros = { git = "https://github.com/glihm/starknet-abigen-rs", tag = "v0.1.3" } 26 | starknet = "0.7.0" 27 | ``` 28 | 29 | ```rust 30 | // Your main.rs or other rust file: 31 | ... 32 | use starknet_abigen_parser; 33 | use starknet_abigen_macros::abigen; 34 | 35 | abigen!(MyContract, "./path/to/abi.json"); 36 | 37 | #[tokio::main] 38 | async fn main() { 39 | ... 40 | } 41 | ``` 42 | You can find a first simple example in the `examples` folder [here](https://github.com/glihm/starknet-abigen-rs/tree/5cde6560bdaf273f1bea779b73b5e2c53cabc2c2/examples). 43 | 44 | ## Quick start 45 | 46 | 1. Terminal 1: Run Katana 47 | 48 | ```sh 49 | dojoup -v nightly 50 | ``` 51 | 52 | ```sh 53 | katana 54 | ``` 55 | 56 | 2. Terminal 2: Contracts setup 57 | 58 | ```sh 59 | cd contracts && scarb build && make setup 60 | ``` 61 | 62 | ```sh 63 | cargo run --example simple_get_set 64 | ``` 65 | 66 | ## Cairo - Rust similarity 67 | 68 | We've tried to leverage the similarity between Rust and Cairo. 69 | With this in mind, the bindings are generated to be as natural as possible from a Rust perspective. 70 | 71 | So most of the types are Rust types, and the basic value for us is the `FieldElement` from `starknet-rs`. 72 | 73 | ```rust 74 | // Cairo: fn get_data(self: @ContractState) -> Span 75 | fn get_data() -> Vec 76 | 77 | // Cairo: fn get_opt(self: @ContractState, val: u32) -> Option 78 | fn get_opt(val: u32) -> Option 79 | 80 | // Cairo: struct MyData { a: felt252, b: u32, c: Span } 81 | struct MyData { 82 | a: FieldElement, 83 | b: u32, 84 | c: Vec, 85 | } 86 | ``` 87 | 88 | If you want to leverage the (de)serialization generated by the bindings, to make raw calls with `starknet-rs`, you can: 89 | 90 | ```rust 91 | let d = MyData { 92 | a: FieldElement::TWO, 93 | b: 123_u32, 94 | c: vec![8, 9], 95 | }; 96 | 97 | let felts = MyData::serialize(&d); 98 | 99 | let felts = vec![FieldElement::ONE, FieldElement::TWO]; 100 | // For now you have to provide the index. Later an other method will consider deserialization from index 0. 101 | let values = Vec::::deserialize(felts, 0).unwrap; 102 | ``` 103 | 104 | Any type implementing the `CairoType` trait can be used this way. 105 | 106 | ## Generate the binding for your contracts 107 | 108 | 1. If you have a large ABI, consider adding a file (at the same level of your `Cargo.toml`) with the `JSON` containing the ABI. 109 | Then you can load the whole file using: 110 | 111 | ```rust 112 | abigen!(MyContract, "./mycontract.abi.json") 113 | ``` 114 | 115 | 2. (DISABLED FOR NOW) If you only want to make a quick call without too much setup, you can paste an ABI directly using: 116 | 117 | ```rust 118 | abigen!(MyContract, r#" 119 | [ 120 | { 121 | "type": "function", 122 | "name": "get_val", 123 | "inputs": [], 124 | "outputs": [ 125 | { 126 | "type": "core::felt252" 127 | } 128 | ], 129 | "state_mutability": "view" 130 | } 131 | ] 132 | "#); 133 | ``` 134 | 135 | 3. To extract ABI from your contract, please use the tool `jq` if you are in local, or any starknet explorer. 136 | With jq, you can do: `cat target/dev/my_contract.contract_class.json | jq .abi > /path/abi.json`. 137 | 138 | ## How to work with events 139 | 140 | Events are special structs/enum that we usually want to deserialize effectively. 141 | The `abigen!` macro generate all the events associated types, and this always include 142 | one enum always named `Event`. 143 | 144 | Any contract you use `abigen!` on will contain this enum, and this also includes the convertion 145 | from `EmittedEvent`, which is the `starknet-rs` type returned when we fetch events. 146 | 147 | So you can do this: 148 | 149 | ```rust 150 | // the Event enum is always declared if at least 1 event is present 151 | // in the cairo file. 152 | use myContract::{Event as AnyEvent}; 153 | 154 | let events = provider.fetch_events(...); 155 | 156 | for e in events { 157 | // The `TryFrom` is already implemented for each variant, which includes 158 | // the deserialization of the variant. 159 | let my_event: AnyEvent = match e.try_into() { 160 | Ok(ev) => ev, 161 | Err(_s) => { 162 | // An event from other contracts, ignore. 163 | continue; 164 | } 165 | }; 166 | 167 | // Then, you can safely check which event it is, and work with it, 168 | // with the rust type! 169 | match my_event { 170 | AnyEvent::MyEventA(a) => { 171 | // do stuff with a. 172 | } 173 | AnyEvent::MyEventB(b) => { 174 | // do stuff with b. 175 | } 176 | ... 177 | }; 178 | } 179 | ``` 180 | 181 | ## Serialization 182 | 183 | Cairo serializes everything as `felt252`. Some edge cases to have in mind: 184 | 185 | 1. Enum 186 | 187 | Enumerations are serialized with the index of the variant first, and then the value (is any). 188 | 189 | ```rust 190 | enum MyEnum { 191 | V1: u128, 192 | V2, 193 | } 194 | 195 | let a = MyEnum::V1(2_u128); 196 | let b = MyEnum::V2; 197 | ``` 198 | 199 | Will be serialized like this, with enum variant index first: 200 | 201 | ``` 202 | a: [0, 2] 203 | b: [1] 204 | ``` 205 | 206 | 2. Span/Array 207 | 208 | After serialization, `Span` and `Array` are processed in the same fashion. 209 | The length is serialized first, and then the following elements. 210 | 211 | ```rust 212 | let a = array![]; 213 | let b = array![1, 2]; 214 | ``` 215 | 216 | Will be serialized as: 217 | 218 | ``` 219 | a: [0] 220 | b: [2, 1, 2] 221 | ``` 222 | 223 | 3. Struct 224 | 225 | `struct` are serialized as their fields define it. There is no length at the beginning. It depends on the fields order. 226 | 227 | ```rust 228 | struct MyStruct { 229 | a: felt252, 230 | b: u256, 231 | c: Array, 232 | } 233 | 234 | let s = MyStruct { 235 | a: 123, 236 | b: 1_u256, 237 | c: array![9], 238 | } 239 | ``` 240 | 241 | Will be serialized as: 242 | 243 | ``` 244 | [123, 1, 0, 1, 9] 245 | ``` 246 | 247 | ## PR on starknet-rs 248 | 249 | The goal of this work was to be included in `starknet-rs` library. 250 | You can follow the status of such process checking those PRs: 251 | 252 | 1. https://github.com/xJonathanLEI/starknet-rs/pull/475 253 | 2. Please take a look to this [README](https://github.com/glihm/starknet-rs/tree/abigen/starknet-macros) for the newest information about the syntax being worked on. 254 | 255 | But we may choose a standalone path in the future to add more features. 256 | 257 | ## Disclaimer 258 | 259 | This is a very early stage of the project. The idea is to have a first version that can be revised by the community and then enhanced. 260 | 261 | Hopefully one day we can have a great lib that can be integrated to `starknet-rs` or remain a stand alone crate which can be combined with `starknet-rs`. 262 | 263 | ## Credits 264 | 265 | None of these crates would have been possible without the great work done in: 266 | 267 | - [`ethers-rs`](https://github.com/gakonst/ethers-rs/) 268 | - [`alloy-rs`](https://github.com/alloy-rs/core/) 269 | - [`starknet-rs`](https://github.com/xJonathanLEI/starknet-rs/) 270 | -------------------------------------------------------------------------------- /contracts/Makefile: -------------------------------------------------------------------------------- 1 | config := --account katana-0 \ 2 | --rpc http://0.0.0.0:5050 3 | 4 | setup: setup_simple_get_set 5 | 6 | # Declare and deploy the simple_get_set contract on katana. 7 | setup_simple_get_set: 8 | $(eval class_hash=$(shell starkli class-hash target/dev/contracts_simple_get_set.contract_class.json)) 9 | starkli declare target/dev/contracts_simple_get_set.contract_class.json ${config} 10 | starkli deploy ${class_hash} --salt 0x1234 ${config} 11 | 12 | # # Declare and deploy the basic contract on katana. 13 | # setup_basic: 14 | # $(eval class_hash=$(shell starkli class-hash target/dev/contracts_basic.sierra.json)) 15 | # starkli declare target/dev/contracts_basic.sierra.json ${config} 16 | # starkli deploy ${class_hash} --salt 0x1234 ${config} 17 | 18 | # # Declare and deploy the basic contract on katana. 19 | # setup_gen: 20 | # $(eval class_hash=$(shell starkli class-hash target/dev/contracts_gen.sierra.json)) 21 | # starkli declare target/dev/contracts_gen.sierra.json ${config} 22 | # starkli deploy ${class_hash} --salt 0x1234 ${config} 23 | 24 | # # Declare and deploy the event contract on katana. 25 | # setup_event: 26 | # $(eval class_hash=$(shell starkli class-hash target/dev/contracts_event.sierra.json)) 27 | # starkli declare target/dev/contracts_event.sierra.json ${config} 28 | # starkli deploy ${class_hash} --salt 0x1234 ${config} 29 | -------------------------------------------------------------------------------- /contracts/Scarb.lock: -------------------------------------------------------------------------------- 1 | # Code generated by scarb DO NOT EDIT. 2 | version = 1 3 | 4 | [[package]] 5 | name = "contracts" 6 | version = "0.1.0" 7 | -------------------------------------------------------------------------------- /contracts/Scarb.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "contracts" 3 | version = "0.1.0" 4 | 5 | [dependencies] 6 | starknet = ">=2.2.0" 7 | 8 | [[target.starknet-contract]] 9 | sierra = true 10 | casm = true 11 | -------------------------------------------------------------------------------- /contracts/abi/simple_get_set.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "function", 4 | "name": "get_a", 5 | "inputs": [], 6 | "outputs": [ 7 | { 8 | "type": "core::felt252" 9 | } 10 | ], 11 | "state_mutability": "view" 12 | }, 13 | { 14 | "type": "function", 15 | "name": "set_a", 16 | "inputs": [ 17 | { 18 | "name": "a", 19 | "type": "core::felt252" 20 | } 21 | ], 22 | "outputs": [], 23 | "state_mutability": "external" 24 | }, 25 | { 26 | "type": "struct", 27 | "name": "core::integer::u256", 28 | "members": [ 29 | { 30 | "name": "low", 31 | "type": "core::integer::u128" 32 | }, 33 | { 34 | "name": "high", 35 | "type": "core::integer::u128" 36 | } 37 | ] 38 | }, 39 | { 40 | "type": "function", 41 | "name": "get_b", 42 | "inputs": [], 43 | "outputs": [ 44 | { 45 | "type": "core::integer::u256" 46 | } 47 | ], 48 | "state_mutability": "view" 49 | }, 50 | { 51 | "type": "function", 52 | "name": "set_b", 53 | "inputs": [ 54 | { 55 | "name": "b", 56 | "type": "core::integer::u256" 57 | } 58 | ], 59 | "outputs": [], 60 | "state_mutability": "external" 61 | }, 62 | { 63 | "type": "event", 64 | "name": "contracts::simple_get_set::simple_get_set::Event", 65 | "kind": "enum", 66 | "variants": [] 67 | } 68 | ] 69 | -------------------------------------------------------------------------------- /contracts/src/basic.cairo: -------------------------------------------------------------------------------- 1 | #[starknet::contract] 2 | mod basic { 3 | #[storage] 4 | struct Storage { 5 | v1: felt252, 6 | v2: u256, 7 | v3: felt252, 8 | } 9 | 10 | #[external(v0)] 11 | fn set_storage(ref self: ContractState, v1: felt252, v2: u256) { 12 | self.v1.write(v1); 13 | self.v2.write(v2); 14 | } 15 | 16 | #[external(v0)] 17 | fn read_storage_tuple(self: @ContractState) -> (felt252, u256) { 18 | (self.v1.read(), self.v2.read()) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /contracts/src/event.cairo: -------------------------------------------------------------------------------- 1 | #[starknet::contract] 2 | mod event { 3 | use starknet::ContractAddress; 4 | 5 | #[storage] 6 | struct Storage { 7 | } 8 | 9 | #[event] 10 | #[derive(Drop, starknet::Event)] 11 | enum Event { 12 | MyEventA: MyEventA, 13 | MyEventB: MyEventB, 14 | MyEventC: MyEventC, 15 | } 16 | 17 | #[derive(Drop, starknet::Event)] 18 | struct MyEventA { 19 | #[key] 20 | header: felt252, 21 | value: Span, 22 | } 23 | 24 | #[derive(Drop, starknet::Event)] 25 | struct MyEventB { 26 | value: felt252, 27 | } 28 | 29 | #[derive(Drop, starknet::Event)] 30 | struct MyEventC { 31 | #[key] 32 | v1: felt252, 33 | #[key] 34 | v2: felt252, 35 | v3: felt252, 36 | v4: ContractAddress, 37 | } 38 | 39 | #[external(v0)] 40 | fn read(ref self: ContractState) -> felt252 { 41 | 2 42 | } 43 | 44 | #[external(v0)] 45 | fn emit_a(ref self: ContractState, header: felt252, value: Span) { 46 | self.emit(MyEventA { 47 | header, 48 | value, 49 | }); 50 | } 51 | 52 | #[external(v0)] 53 | fn emit_b(ref self: ContractState, value: felt252) { 54 | self.emit(MyEventB { 55 | value, 56 | }); 57 | } 58 | 59 | #[external(v0)] 60 | fn emit_c(ref self: ContractState, v1: felt252, v2: felt252, v3: felt252, v4: ContractAddress) { 61 | self.emit(MyEventC { 62 | v1, 63 | v2, 64 | v3, 65 | v4, 66 | }); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /contracts/src/gen.cairo: -------------------------------------------------------------------------------- 1 | #[starknet::contract] 2 | mod gen { 3 | use starknet::ContractAddress; 4 | 5 | #[storage] 6 | struct Storage { 7 | v1: felt252, 8 | v2: felt252, 9 | } 10 | 11 | #[derive(Serde, Drop)] 12 | struct MyStruct { 13 | f1: felt252, 14 | f2: T, 15 | f3: felt252, 16 | } 17 | 18 | #[external(v0)] 19 | fn func1(ref self: ContractState, a: MyStruct) { 20 | self.v1.write(a.f1); 21 | self.v2.write(a.f2); 22 | } 23 | 24 | #[external(v0)] 25 | fn func2(ref self: ContractState, a: MyStruct) { 26 | self.v1.write(a.f2.low.into()); 27 | self.v2.write(a.f2.high.into()); 28 | 29 | } 30 | 31 | #[external(v0)] 32 | fn read(self: @ContractState) -> (felt252, felt252) { 33 | (self.v1.read(), self.v2.read()) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /contracts/src/lib.cairo: -------------------------------------------------------------------------------- 1 | mod basic; 2 | mod gen; 3 | mod event; 4 | mod simple_get_set; 5 | 6 | -------------------------------------------------------------------------------- /contracts/src/simple_get_set.cairo: -------------------------------------------------------------------------------- 1 | #[starknet::contract] 2 | mod simple_get_set { 3 | #[storage] 4 | struct Storage { 5 | a: felt252, 6 | b: u256, 7 | } 8 | 9 | #[external(v0)] 10 | fn get_a(self: @ContractState) -> felt252 { 11 | self.a.read() 12 | } 13 | 14 | #[external(v0)] 15 | fn set_a(ref self: ContractState, a: felt252) { 16 | self.a.write(a); 17 | } 18 | 19 | #[external(v0)] 20 | fn get_b(self: @ContractState) -> u256 { 21 | self.b.read() 22 | } 23 | 24 | #[external(v0)] 25 | fn set_b(ref self: ContractState, b: u256) { 26 | self.b.write(b); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /crates/macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "starknet-abigen-macros" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | proc-macro = true 8 | 9 | [dependencies] 10 | anyhow.workspace = true 11 | starknet.workspace = true 12 | starknet-abigen-parser.workspace = true 13 | proc-macro2 = "1.0" 14 | quote = "1.0" 15 | syn = "2.0.15" 16 | serde_json = "1.0.74" 17 | -------------------------------------------------------------------------------- /crates/macros/README.md: -------------------------------------------------------------------------------- 1 | # Procedural macros for `starknet-abigen-rs` 2 | 3 | ## abigen 4 | 5 | The `abigen` macro aims at generating rust binding from an `ABI` of a smart contract. 6 | The generated bindings contains all the functions, events, structs and enums that are 7 | present in the `ABI` file. 8 | 9 | Some types are directly mapped to rust native types (like integers, `Result`, `Option`, boolean etc..), 10 | other specific types like `ContractAddress`, `ClassHash` or `EthAddress` are managed by `starknet-rs`, 11 | and all other types found in the `ABI` are generated as `struct` or `enum` as necessary. 12 | 13 | `abigen` will generate all the serialization/deserialization code that is required to 14 | work with plain rust types. 15 | 16 | For instance: 17 | 18 | ```rust,ignore 19 | // Cairo function like fn view_1(self: @ContractState, v: felt252, s: Span) 20 | // is generated in rust like: 21 | 22 | fn view_1(v: FieldElement, s: Vec); 23 | ``` 24 | 25 | To generate the bindings for your contract, you can do the following: 26 | 27 | ```rust,ignore 28 | use starknet::macros::abigen; 29 | 30 | abigen!(MyContract, "/path/to/abi.json"); 31 | ``` 32 | 33 | This will generate all the types and two `struct` for the contract: 34 | 35 | 1. `MyContractReader`, which is use to call `view` functions that are only reading the blockchain state. 36 | To initialize a reader, you need your contract address and a provider: 37 | 38 | ```rust,ignore 39 | let rpc_url = Url::parse("http://0.0.0.0:5050").unwrap(); 40 | let provider = JsonRpcClient::new(HttpTransport::new(rpc_url.clone())); 41 | let contract_address = FieldElement::from_hex_be("0x123...").unwrap(); 42 | 43 | let reader = MyContractReader::new(contract_address, &provider); 44 | let result = reader.my_view_1().await; 45 | ``` 46 | 47 | 2. `MyContract`, which in turn is used to call `external` functions, where a transaction is actually sent to the blockchain. 48 | This one requires an account, to sign those transactions: 49 | 50 | ```rust,ignore 51 | let rpc_url = Url::parse("http://0.0.0.0:5050").unwrap(); 52 | let provider = JsonRpcClient::new(HttpTransport::new(rpc_url.clone())); 53 | 54 | let signer = LocalWallet::from(SigningKey::from_secret_scalar( 55 | FieldElement::from_hex_be("").unwrap(), 56 | )); 57 | 58 | let account_address = FieldElement::from_hex_be("").unwrap(); 59 | let account = SingleOwnerAccount::new( 60 | provider.clone(), 61 | signer, 62 | address, 63 | felt!("0x4b4154414e41"), // KATANA 64 | ExecutionEncoding::Legacy, 65 | ); 66 | 67 | let contract_address = FieldElement::from_hex_be("0x123...").unwrap(); 68 | 69 | let reader = MyContract::new(contract_address, &account); 70 | let result = reader.my_external_1().await; 71 | ``` 72 | 73 | An other feature provided by `abigen` macro is the capabilities of deserialiazing events. 74 | In the `ABI`, there is always an `Event` enum, which contains all the events declared in your contract. 75 | 76 | You can then do the following: 77 | 78 | ```rust,ignore 79 | let even_page = provider.fetch_events(...); 80 | for e in event_page.events { 81 | let my_event: Event = match e.try_into() { 82 | Ok(ev) => ev, 83 | Err(_) => continue; // This is an event from an other contract or you may use an out-dated ABI. 84 | }; 85 | 86 | match my_event { 87 | Event::MyEventA(a) => // work with a, already typed and deserialized, 88 | Event::MyEventB(b) => // work with b, already typed and deserialized, 89 | ... 90 | }; 91 | } 92 | ``` 93 | -------------------------------------------------------------------------------- /crates/macros/src/abigen/contract_abi.rs: -------------------------------------------------------------------------------- 1 | //! Defines the arguments of the `abigen` macro. 2 | //! 3 | //! `ContractAbi` is expected to the argument 4 | //! passed to the macro. We should then parse the 5 | //! token stream to ensure the arguments are correct. 6 | //! 7 | //! At this moment, the macro supports two fashions: 8 | //! 9 | //! Loading from a file. 10 | //! 11 | //! abigen!(ContractName, "path/to/abi.json" 12 | //! 13 | //! 14 | //! Loading from a literal string ABI. 15 | //! 16 | //! abigen!(ContractName, r#" 17 | //! [{ .... }] 18 | //! "#); 19 | //! 20 | use starknet::core::types::contract::AbiEntry; 21 | use std::fs::File; 22 | use syn::{ 23 | parse::{Parse, ParseStream, Result}, 24 | Ident, LitStr, Token, 25 | }; 26 | 27 | #[derive(Clone, Debug)] 28 | pub(crate) struct ContractAbi { 29 | pub name: Ident, 30 | pub abi: Vec, 31 | } 32 | 33 | impl Parse for ContractAbi { 34 | fn parse(input: ParseStream) -> Result { 35 | let name = input.parse::()?; 36 | input.parse::()?; 37 | 38 | // Path rooted to the Cargo.toml location. 39 | let json_path = input.parse::()?; 40 | 41 | let abi = 42 | serde_json::from_reader::<_, Vec>(File::open(json_path.value()).map_err( 43 | |e| syn::Error::new(json_path.span(), format!("JSON open file error: {}", e)), 44 | )?) 45 | .map_err(|e| syn::Error::new(json_path.span(), format!("JSON parse error: {}", e)))?; 46 | 47 | Ok(ContractAbi { name, abi }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /crates/macros/src/abigen/expand/contract.rs: -------------------------------------------------------------------------------- 1 | //! Expands the contract first implementation with 2 | //! default configuration for provider and account, if any. 3 | use proc_macro2::TokenStream as TokenStream2; 4 | use quote::quote; 5 | use syn::Ident; 6 | 7 | use super::utils; 8 | 9 | pub struct CairoContract; 10 | 11 | impl CairoContract { 12 | pub fn expand(contract_name: Ident) -> TokenStream2 { 13 | let reader = utils::str_to_ident(format!("{}Reader", contract_name).as_str()); 14 | let q = quote! { 15 | 16 | #[derive(Debug)] 17 | pub struct #contract_name { 18 | pub address: starknet::core::types::FieldElement, 19 | pub account: A, 20 | } 21 | 22 | impl #contract_name { 23 | pub fn new(address: starknet::core::types::FieldElement, account: A) -> Self { 24 | Self { address, account } 25 | } 26 | 27 | pub fn reader(&self) -> #reader { 28 | #reader::new(self.address, self.account.provider()) 29 | } 30 | } 31 | 32 | #[derive(Debug)] 33 | pub struct #reader<'a, P: starknet::providers::Provider + Sync> { 34 | pub address: starknet::core::types::FieldElement, 35 | pub provider: &'a P, 36 | call_block_id: starknet::core::types::BlockId, 37 | } 38 | 39 | impl<'a, P: starknet::providers::Provider + Sync> #reader<'a, P> { 40 | pub fn new( 41 | address: starknet::core::types::FieldElement, 42 | provider: &'a P, 43 | ) -> Self { 44 | let call_block_id = starknet::core::types::BlockId::Tag(starknet::core::types::BlockTag::Pending); 45 | Self { address, provider, call_block_id } 46 | } 47 | 48 | pub fn set_call_block_id(&mut self, block_id: starknet::core::types::BlockId) { 49 | self.call_block_id = block_id; 50 | } 51 | 52 | pub fn get_call_block_id(&self) -> starknet::core::types::BlockId { 53 | self.call_block_id 54 | } 55 | 56 | // TODO: String is not the ideal, but we need to export an enum somewhere for that. 57 | pub async fn get_tx_status(&self, transaction_hash: FieldElement) -> String { 58 | use starknet::{ 59 | core::types::{ExecutionResult, FieldElement, StarknetError}, 60 | providers::{MaybeUnknownErrorCode, Provider, ProviderError, StarknetErrorWithMessage}, 61 | }; 62 | 63 | match self.provider.get_transaction_receipt(transaction_hash).await { 64 | Ok(receipt) => match receipt.execution_result() { 65 | ExecutionResult::Succeeded => { 66 | "ok".to_string() 67 | } 68 | ExecutionResult::Reverted { reason } => { 69 | format!("reverted: {}", reason) 70 | } 71 | }, 72 | Err(ProviderError::StarknetError(StarknetErrorWithMessage { 73 | code: MaybeUnknownErrorCode::Known(StarknetError::TransactionHashNotFound), 74 | .. 75 | })) => { 76 | "pending".to_string() 77 | } 78 | // Some nodes are still serving error code `25` for tx hash not found. This is 79 | // technically a bug on the node's side, but we maximize compatibility here by also 80 | // accepting it. 81 | Err(ProviderError::StarknetError(StarknetErrorWithMessage { 82 | code: MaybeUnknownErrorCode::Known(StarknetError::InvalidTransactionIndex), 83 | .. 84 | })) => { 85 | "pending".to_string() 86 | } 87 | Err(err) => format!("error: {err}") 88 | } 89 | } 90 | 91 | } 92 | }; 93 | 94 | q 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /crates/macros/src/abigen/expand/enum.rs: -------------------------------------------------------------------------------- 1 | //! Enums expansion, taking in account generic types if any. 2 | use super::{ 3 | generic, 4 | utils::{str_to_ident, str_to_type}, 5 | Expandable, 6 | }; 7 | 8 | use starknet_abigen_parser::{ 9 | abi_types::{AbiType, AbiTypeAny}, 10 | CairoEnum, 11 | }; 12 | 13 | use proc_macro2::TokenStream as TokenStream2; 14 | use quote::quote; 15 | use syn::Ident; 16 | 17 | impl Expandable for CairoEnum { 18 | fn expand_decl(&self) -> TokenStream2 { 19 | let enum_name = str_to_ident(&self.get_name()); 20 | 21 | let mut variants: Vec = vec![]; 22 | 23 | for (name, abi_type) in &self.variants { 24 | let name = str_to_ident(name); 25 | let ty = str_to_type(&abi_type.to_rust_type()); 26 | if abi_type.is_unit() { 27 | variants.push(quote!(#name)); 28 | } else { 29 | variants.push(quote!(#name(#ty))); 30 | } 31 | } 32 | 33 | if self.is_generic() { 34 | let gentys: Vec = self.get_gentys().iter().map(|g| str_to_ident(g)).collect(); 35 | 36 | quote! { 37 | #[derive(Debug, PartialEq)] 38 | pub enum #enum_name<#(#gentys),*> { 39 | #(#variants),* 40 | } 41 | } 42 | } else { 43 | quote! { 44 | #[derive(Debug, PartialEq)] 45 | pub enum #enum_name { 46 | #(#variants),* 47 | } 48 | } 49 | } 50 | } 51 | 52 | fn expand_impl(&self) -> TokenStream2 { 53 | let name_str = &self.get_name(); 54 | let enum_name = str_to_ident(name_str); 55 | 56 | let mut serialized_sizes: Vec = vec![]; 57 | let mut serializations: Vec = vec![]; 58 | let mut deserializations: Vec = vec![]; 59 | 60 | for (i, (name, abi_type)) in self.variants.iter().enumerate() { 61 | let variant_name = str_to_ident(name); 62 | let ty = str_to_type(&abi_type.to_rust_type_path()); 63 | 64 | // Tuples type used as rust type item path must be surrounded 65 | // by angle brackets. 66 | let ty_punctuated = match abi_type { 67 | AbiTypeAny::Tuple(_) => quote!(<#ty>), 68 | _ => quote!(#ty), 69 | }; 70 | 71 | if abi_type.is_unit() { 72 | serializations.push(quote! { 73 | #enum_name::#variant_name => usize::serialize(&#i) 74 | }); 75 | deserializations.push(quote! { 76 | #i => Ok(#enum_name::#variant_name) 77 | }); 78 | serialized_sizes.push(quote! { 79 | #enum_name::#variant_name => 1 80 | }); 81 | } else { 82 | serializations.push(quote! { 83 | #enum_name::#variant_name(val) => { 84 | let mut temp = vec![]; 85 | temp.extend(usize::serialize(&#i)); 86 | temp.extend(#ty_punctuated::serialize(val)); 87 | temp 88 | } 89 | }); 90 | deserializations.push(quote! { 91 | #i => Ok(#enum_name::#variant_name(#ty_punctuated::deserialize(__felts, __offset + 1)?)) 92 | }); 93 | // +1 because we have to handle the variant index also. 94 | serialized_sizes.push(quote! { 95 | #enum_name::#variant_name(val) => #ty_punctuated::serialized_size(val) + 1 96 | }) 97 | } 98 | } 99 | 100 | deserializations.push(quote! { 101 | _ => panic!("Index not handle for enum {}", #name_str) 102 | }); 103 | 104 | let gentys: Vec = self.get_gentys().iter().map(|g| str_to_ident(g)).collect(); 105 | 106 | let impl_line = if self.is_generic() { 107 | generic::impl_with_gentys_tokens(&enum_name, &gentys) 108 | } else { 109 | quote!(impl starknet_abigen_parser::cairo_types::CairoType for #enum_name) 110 | }; 111 | 112 | let rust_type = if self.is_generic() { 113 | generic::rust_associated_type_gentys_tokens(&enum_name, &gentys) 114 | } else { 115 | quote!( 116 | type RustType = Self; 117 | ) 118 | }; 119 | 120 | quote! { 121 | #impl_line { 122 | 123 | #rust_type 124 | 125 | const SERIALIZED_SIZE: std::option::Option = std::option::Option::None; 126 | 127 | #[inline] 128 | fn serialized_size(__rust: &Self::RustType) -> usize { 129 | match __rust { 130 | #(#serialized_sizes),* 131 | } 132 | } 133 | 134 | fn serialize(__rust: &Self::RustType) -> Vec { 135 | match __rust { 136 | #(#serializations),* 137 | } 138 | } 139 | 140 | fn deserialize(__felts: &[starknet::core::types::FieldElement], __offset: usize) -> starknet_abigen_parser::cairo_types::Result { 141 | let __index:u128 = __felts[__offset].try_into().unwrap(); 142 | match __index as usize { 143 | #(#deserializations),* 144 | } 145 | 146 | } 147 | } 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /crates/macros/src/abigen/expand/event.rs: -------------------------------------------------------------------------------- 1 | //! Events expansion. 2 | use super::{ 3 | utils::{str_to_ident, str_to_litstr, str_to_type}, 4 | Expandable, ExpandableEvent, 5 | }; 6 | 7 | use starknet::core::types::contract::EventFieldKind; 8 | use starknet_abigen_parser::{ 9 | abi_types::{AbiType, AbiTypeAny}, 10 | CairoEvent, CairoEventInner, 11 | }; 12 | 13 | use proc_macro2::TokenStream as TokenStream2; 14 | use quote::quote; 15 | 16 | impl ExpandableEvent for CairoEvent { 17 | fn expand_decl(&self) -> TokenStream2 { 18 | let decl = match &self.inner { 19 | CairoEventInner::Struct(s) => s.expand_decl(), 20 | CairoEventInner::Enum(e) => e.expand_decl(), 21 | }; 22 | quote!(#decl) 23 | } 24 | 25 | fn expand_impl(&self, events: &[CairoEvent]) -> TokenStream2 { 26 | let mut tokens = vec![]; 27 | 28 | let inner_imp = match &self.inner { 29 | CairoEventInner::Struct(s) => s.expand_impl(), 30 | CairoEventInner::Enum(e) => e.expand_impl(), 31 | }; 32 | 33 | tokens.push(quote!(#inner_imp)); 34 | 35 | // Generate the get_selector() method for this event. 36 | let name_ident = str_to_ident(&self.get_name()); 37 | let name_str = str_to_litstr(&self.get_name()); 38 | let selector = quote! { 39 | impl #name_ident { 40 | pub fn get_selector() -> starknet::core::types::FieldElement { 41 | starknet::macros::selector!(#name_str) 42 | } 43 | } 44 | }; 45 | 46 | tokens.push(selector); 47 | 48 | // Stop here if it's not the Event enum. 49 | if self.get_name() != "Event" { 50 | return quote! { 51 | #(#tokens)* 52 | }; 53 | } 54 | 55 | // If it's the Event enum, we can generate the TryFrom. 56 | 57 | // It should always be an enum here. 58 | if let CairoEventInner::Enum(inner) = &self.inner { 59 | let mut variants_tokens = vec![]; 60 | 61 | for (v_name, _) in &inner.variants { 62 | // Get the corresponding CairoEvent in the array to access it's fields. 63 | let cev = events 64 | .iter() 65 | .find(|&e| &e.get_name() == v_name) 66 | .unwrap_or_else(|| panic!("Event variant {} was not found in events", v_name)); 67 | 68 | let _cev_fields_kinds = cev.count_fields_kinds(); 69 | 70 | let mut desers_tokens = vec![]; 71 | let mut names_tokens = vec![]; 72 | let v_ident = str_to_ident(v_name); 73 | let v_name_str = str_to_litstr(v_name); 74 | 75 | // Let's write the deserialization of each member/variants 76 | // of the current event. 77 | match &cev.inner { 78 | CairoEventInner::Struct(s) => { 79 | for (idx, (name, abi_type)) in s.members.iter().enumerate() { 80 | let kind = &cev.fields_kinds[idx]; 81 | let name_str = str_to_litstr(name); 82 | let name = str_to_ident(name); 83 | let ty = str_to_type(&abi_type.to_rust_type_path()); 84 | let ty_punctuated = match abi_type { 85 | AbiTypeAny::Tuple(_) => quote!(<#ty>), 86 | _ => quote!(#ty), 87 | }; 88 | 89 | match kind { 90 | EventFieldKind::Key => { 91 | desers_tokens.push(quote! { 92 | let #name = match #ty_punctuated::deserialize(&event.keys, key_offset) { 93 | Ok(v) => v, 94 | Err(e) => return Err(format!("Could not deserialize field {} for {}: {:?}", #name_str, #v_name_str, e)), 95 | }; 96 | key_offset += #ty_punctuated::serialized_size(&#name); 97 | }); 98 | } 99 | EventFieldKind::Data => { 100 | desers_tokens.push(quote! { 101 | let #name = match #ty_punctuated::deserialize(&event.data, data_offset) { 102 | Ok(v) => v, 103 | Err(e) => return Err(format!("Could not deserialize field {} for {}: {:?}", #name_str, #v_name_str, e)), 104 | }; 105 | data_offset += #ty_punctuated::serialized_size(&#name); 106 | }); 107 | } 108 | _ => {} 109 | }; 110 | 111 | names_tokens.push(quote!(#name)); 112 | } 113 | } 114 | CairoEventInner::Enum(e) => { 115 | for (idx, (name, abi_type)) in e.variants.iter().enumerate() { 116 | let kind = &cev.fields_kinds[idx]; 117 | let name_str = str_to_litstr(name); 118 | let name = str_to_ident(name); 119 | let ty = str_to_type(&abi_type.to_rust_type_path()); 120 | let ty_punctuated = match abi_type { 121 | AbiTypeAny::Tuple(_) => quote!(<#ty>), 122 | _ => quote!(#ty), 123 | }; 124 | 125 | match kind { 126 | EventFieldKind::Key => { 127 | desers_tokens.push(quote! { 128 | let #name = match #ty_punctuated::deserialize(&event.keys, key_offset) { 129 | Ok(v) => v, 130 | Err(e) => return Err(format!("Could not deserialize field {} for {}: {:?}", #name_str, #v_name_str, e)), 131 | }; 132 | key_offset += #ty_punctuated::serialized_size(&#name); 133 | }); 134 | } 135 | EventFieldKind::Data => { 136 | desers_tokens.push(quote! { 137 | let #name = match #ty_punctuated::deserialize(&event.data, data_offset) { 138 | Ok(v) => v, 139 | Err(e) => return Err(format!("Could not deserialize field {} for {}: {:?}", #name_str, #v_name_str, e)), 140 | }; 141 | data_offset += #ty_punctuated::serialized_size(&#name); 142 | }); 143 | } 144 | _ => {} 145 | }; 146 | 147 | names_tokens.push(quote!(#name)); 148 | } 149 | } 150 | }; 151 | 152 | let variant = quote! { 153 | if selector == #v_ident::get_selector() { 154 | // TODO: add a validation to check keys len and data len. 155 | // To have a nice error message if the event is not formatted as 156 | // expected. 157 | 158 | // We skip the selector. 159 | let mut key_offset = 1; 160 | let mut data_offset = 0; 161 | 162 | #(#desers_tokens)* 163 | 164 | return Ok(Event::#v_ident(#v_ident { 165 | #(#names_tokens),* 166 | })) 167 | }; 168 | }; 169 | 170 | variants_tokens.push(variant); 171 | } 172 | 173 | // TODO: change for custom type instead of str for error? 174 | let try_from = quote! { 175 | impl TryFrom for Event { 176 | type Error = String; 177 | 178 | fn try_from(event: starknet::core::types::EmittedEvent) -> Result { 179 | use starknet_abigen_parser::CairoType; 180 | 181 | if event.keys.is_empty() { 182 | return Err("Missing event selector, no keys found".to_string()); 183 | } 184 | let selector = event.keys[0]; 185 | 186 | #(#variants_tokens)* 187 | 188 | Err(format!("Could not match any event from selector {:#064x}", selector)) 189 | } 190 | } 191 | }; 192 | 193 | tokens.push(try_from); 194 | } 195 | 196 | quote! { 197 | #(#tokens)* 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /crates/macros/src/abigen/expand/function.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | utils::{str_to_ident, str_to_type}, 3 | Expandable, 4 | }; 5 | use proc_macro2::TokenStream as TokenStream2; 6 | use quote::quote; 7 | use starknet::core::types::contract::StateMutability; 8 | use starknet_abigen_parser::{ 9 | abi_types::{AbiType, AbiTypeAny}, 10 | CairoFunction, 11 | }; 12 | 13 | fn get_func_inputs(inputs: &[(String, AbiTypeAny)]) -> Vec { 14 | let mut out: Vec = vec![]; 15 | 16 | for (name, abi_type) in inputs { 17 | let name = str_to_ident(name); 18 | let ty = str_to_type(&abi_type.to_rust_type()); 19 | // We can pass a reference here as serialize always takes a reference. 20 | out.push(quote!(#name:&#ty)); 21 | } 22 | 23 | out 24 | } 25 | 26 | impl Expandable for CairoFunction { 27 | fn expand_decl(&self) -> TokenStream2 { 28 | let func_name = str_to_ident(&self.name); 29 | let inputs = get_func_inputs(&self.inputs); 30 | 31 | let output = match self.state_mutability { 32 | StateMutability::View => match &self.output { 33 | Some(o) => { 34 | let oty = str_to_type(&o.to_rust_type()); 35 | quote!(-> starknet_abigen_parser::cairo_types::Result<#oty>) 36 | } 37 | None => { 38 | quote!(-> starknet_abigen_parser::cairo_types::Result<()>) 39 | } 40 | }, 41 | StateMutability::External => { 42 | quote!(-> Result> 44 | ) 45 | } 46 | }; 47 | 48 | quote! { 49 | pub async fn #func_name( 50 | &self, 51 | #(#inputs),* 52 | ) #output 53 | } 54 | } 55 | 56 | fn expand_impl(&self) -> TokenStream2 { 57 | let decl = self.expand_decl(); 58 | let func_name = &self.name; 59 | 60 | let mut serializations: Vec = vec![]; 61 | for (name, abi_type) in &self.inputs { 62 | let name = str_to_ident(name); 63 | let ty = str_to_type(&abi_type.to_rust_type_path()); 64 | 65 | let ser = match abi_type { 66 | AbiTypeAny::Tuple(_) => quote! { 67 | __calldata.extend(<#ty>::serialize(#name)); 68 | }, 69 | _ => quote!(__calldata.extend(#ty::serialize(#name));), 70 | }; 71 | serializations.push(ser); 72 | } 73 | 74 | let out_res = match &self.output { 75 | Some(o) => { 76 | let out_type_path = str_to_type(&o.to_rust_type_path()); 77 | match o { 78 | // Tuples type used as rust type path must be surrounded 79 | // by LT/GT. 80 | AbiTypeAny::Tuple(_) => quote!(<#out_type_path>::deserialize(&r, 0)), 81 | _ => quote!(#out_type_path::deserialize(&r, 0)), 82 | } 83 | } 84 | None => quote!(Ok(())), 85 | }; 86 | 87 | match &self.state_mutability { 88 | StateMutability::View => quote! { 89 | #[allow(clippy::ptr_arg)] 90 | #decl { 91 | use starknet_abigen_parser::CairoType; 92 | use starknet::core::types::{BlockId, BlockTag}; 93 | 94 | let mut __calldata = vec![]; 95 | #(#serializations)* 96 | 97 | let r = self.provider 98 | .call( 99 | starknet::core::types::FunctionCall { 100 | contract_address: self.address, 101 | entry_point_selector: starknet::macros::selector!(#func_name), 102 | calldata: __calldata, 103 | }, 104 | self.call_block_id, 105 | ) 106 | .await.map_err( 107 | |err| 108 | starknet_abigen_parser::cairo_types::Error::Deserialize( 109 | format!("Deserialization error {}", err)))?; 110 | 111 | #out_res 112 | } 113 | }, 114 | StateMutability::External => { 115 | // Also generate the Call getter for the function. 116 | let func_name_call = str_to_ident(&format!("{}_getcall", self.name)); 117 | let inputs = get_func_inputs(&self.inputs); 118 | 119 | quote! { 120 | pub fn #func_name_call( 121 | &self, 122 | #(#inputs),* 123 | ) -> starknet::accounts::Call { 124 | use starknet_abigen_parser::CairoType; 125 | 126 | let mut __calldata = vec![]; 127 | #(#serializations)* 128 | 129 | starknet::accounts::Call { 130 | to: self.address, 131 | selector: starknet::macros::selector!(#func_name), 132 | calldata: __calldata, 133 | } 134 | } 135 | 136 | #[allow(clippy::ptr_arg)] 137 | #decl { 138 | use starknet_abigen_parser::CairoType; 139 | use starknet::accounts::Account; 140 | 141 | let mut __calldata = vec![]; 142 | #(#serializations)* 143 | 144 | let calls = vec![starknet::accounts::Call { 145 | to: self.address, 146 | selector: starknet::macros::selector!(#func_name), 147 | calldata: __calldata, 148 | }]; 149 | 150 | // TODO: add a way for fee estimation and max fee to be parametrizable. 151 | self.account.execute(calls).send().await 152 | } 153 | 154 | // We can add a function *_estimate_fee? 155 | // 156 | // TODO: How can we add Fee configuration + estimate fee out of the box. 157 | // maybe two methods are generated, one for actually running, the other 158 | // for estimate the fees. 159 | // Or, we can add a config struct as the last argument? Or directly 160 | // at the initialization of the contract, we can give a config for 161 | // fees (manual, estimated + scale factor). 162 | // The estimate only may be done at the function level, to avoid 163 | // altering the contract instance itself and hence races. 164 | 165 | } 166 | } 167 | } 168 | } 169 | } 170 | 171 | #[cfg(test)] 172 | mod tests { 173 | use crate::abigen::Expandable; 174 | use proc_macro2::TokenStream as TokenStream2; 175 | use quote::quote; 176 | use starknet::core::types::contract::StateMutability; 177 | use starknet_abigen_parser::{abi_types::AbiTypeAny, CairoFunction}; 178 | 179 | #[test] 180 | fn test_decl_basic() { 181 | let cf = CairoFunction { 182 | name: "my_func".to_string(), 183 | state_mutability: StateMutability::View, 184 | inputs: vec![ 185 | ("v1".to_string(), AbiTypeAny::Basic("core::felt252".into())), 186 | ("v2".to_string(), AbiTypeAny::Basic("core::felt252".into())), 187 | ], 188 | output: Some(AbiTypeAny::Basic("core::felt252".into())), 189 | }; 190 | let te1 = cf.expand_decl(); 191 | let tef1: TokenStream2 = quote!( 192 | pub async fn my_func(&self, v1: &starknet::core::types::FieldElement, v2: &starknet::core::types::FieldElement) -> starknet_abigen_parser::cairo_types::Result 193 | ); 194 | 195 | assert_eq!(te1.to_string(), tef1.to_string()); 196 | } 197 | 198 | #[test] 199 | fn test_impl_basic() { 200 | let cf = CairoFunction { 201 | name: "my_func".to_string(), 202 | state_mutability: StateMutability::View, 203 | inputs: vec![ 204 | ("v1".to_string(), AbiTypeAny::Basic("core::felt252".into())), 205 | ("v2".to_string(), AbiTypeAny::Basic("core::felt252".into())), 206 | ], 207 | output: Some(AbiTypeAny::Basic("core::felt252".into())), 208 | }; 209 | let te1 = cf.expand_impl(); 210 | 211 | #[rustfmt::skip] 212 | let tef1: TokenStream2 = quote!( 213 | #[allow(clippy::ptr_arg)] 214 | pub async fn my_func( 215 | &self, 216 | v1: &starknet::core::types::FieldElement, 217 | v2: &starknet::core::types::FieldElement 218 | ) -> starknet_abigen_parser::cairo_types::Result { 219 | use starknet_abigen_parser::CairoType; 220 | use starknet::core::types::{BlockId, BlockTag}; 221 | 222 | let mut __calldata = vec![]; 223 | __calldata.extend(starknet::core::types::FieldElement::serialize(v1)); 224 | __calldata.extend(starknet::core::types::FieldElement::serialize(v2)); 225 | 226 | let r = self.provider 227 | .call( 228 | starknet::core::types::FunctionCall { 229 | contract_address: self.address, 230 | entry_point_selector: starknet::macros::selector!("my_func"), 231 | calldata: __calldata, 232 | }, 233 | self.call_block_id, 234 | ) 235 | .await.map_err(|err| starknet_abigen_parser::cairo_types::Error::Deserialize(format!("Deserialization error {}" , err)))?; 236 | 237 | starknet::core::types::FieldElement::deserialize(&r, 0) 238 | } 239 | ); 240 | 241 | assert_eq!(te1.to_string(), tef1.to_string()); 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /crates/macros/src/abigen/expand/generic.rs: -------------------------------------------------------------------------------- 1 | //! Utils functions for generic expansion. 2 | use super::utils::str_to_ident; 3 | 4 | use proc_macro2::TokenStream as TokenStream2; 5 | use quote::quote; 6 | use syn::Ident; 7 | 8 | /// Expands the implementation line with generic types. 9 | pub fn impl_with_gentys_tokens(entity_name: &Ident, gentys: &Vec) -> TokenStream2 { 10 | let gentys_rust: Vec = gentys 11 | .iter() 12 | .map(|g| str_to_ident(format!("R{}", g).as_str())) 13 | .collect(); 14 | 15 | let mut tokens = vec![]; 16 | 17 | tokens.push(quote! { 18 | impl<#(#gentys),* , #(#gentys_rust),*> starknet_abigen_parser::CairoType for #entity_name<#(#gentys),*> 19 | where 20 | }); 21 | 22 | for (i, g) in gentys.iter().enumerate() { 23 | let gr = &gentys_rust[i]; 24 | tokens.push(quote!(#g: starknet_abigen_parser::CairoType,)); 25 | } 26 | 27 | quote!(#(#tokens)*) 28 | } 29 | 30 | /// Expands the associated types lines for generic types. 31 | pub fn rust_associated_type_gentys_tokens(entity_name: &Ident, gentys: &[Ident]) -> TokenStream2 { 32 | let gentys_rust: Vec = gentys 33 | .iter() 34 | .map(|g| str_to_ident(format!("R{}", g).as_str())) 35 | .collect(); 36 | 37 | quote!(type RustType = #entity_name<#(#gentys_rust),*>;) 38 | } 39 | -------------------------------------------------------------------------------- /crates/macros/src/abigen/expand/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod contract; 2 | pub(crate) mod r#enum; 3 | pub(crate) mod event; 4 | pub(crate) mod function; 5 | pub(crate) mod generic; 6 | pub(crate) mod r#struct; 7 | pub(crate) mod utils; 8 | 9 | use proc_macro2::TokenStream as TokenStream2; 10 | use starknet_abigen_parser::CairoEvent; 11 | 12 | pub trait Expandable { 13 | fn expand_decl(&self) -> TokenStream2; 14 | fn expand_impl(&self) -> TokenStream2; 15 | } 16 | 17 | pub trait ExpandableEvent { 18 | fn expand_decl(&self) -> TokenStream2; 19 | fn expand_impl(&self, events: &[CairoEvent]) -> TokenStream2; 20 | } 21 | -------------------------------------------------------------------------------- /crates/macros/src/abigen/expand/struct.rs: -------------------------------------------------------------------------------- 1 | //! Struct expansion, taking in account generic types if any. 2 | use super::{ 3 | generic, 4 | utils::{str_to_ident, str_to_type}, 5 | Expandable, 6 | }; 7 | 8 | use starknet_abigen_parser::{ 9 | abi_types::{AbiType, AbiTypeAny}, 10 | CairoStruct, 11 | }; 12 | 13 | use proc_macro2::TokenStream as TokenStream2; 14 | use quote::quote; 15 | use syn::Ident; 16 | 17 | impl Expandable for CairoStruct { 18 | fn expand_decl(&self) -> TokenStream2 { 19 | let struct_name = str_to_ident(&self.get_name()); 20 | 21 | let mut members: Vec = vec![]; 22 | for (name, abi_type) in &self.members { 23 | let name = str_to_ident(name); 24 | let ty = str_to_type(&abi_type.to_rust_type()); 25 | 26 | members.push(quote!(#name: #ty)); 27 | } 28 | 29 | if self.is_generic() { 30 | let gentys: Vec = self.get_gentys().iter().map(|g| str_to_ident(g)).collect(); 31 | 32 | quote! { 33 | #[derive(Debug, PartialEq)] 34 | pub struct #struct_name<#(#gentys),*> { 35 | #(pub #members),* 36 | } 37 | } 38 | } else { 39 | quote! { 40 | #[derive(Debug, PartialEq)] 41 | pub struct #struct_name { 42 | #(pub #members),* 43 | } 44 | } 45 | } 46 | } 47 | 48 | fn expand_impl(&self) -> TokenStream2 { 49 | let struct_name = str_to_ident(&self.get_name()); 50 | 51 | let mut sizes: Vec = vec![]; 52 | let mut sers: Vec = vec![]; 53 | let mut desers: Vec = vec![]; 54 | let mut names: Vec = vec![]; 55 | 56 | let mut is_first = true; 57 | for (name, abi_type) in &self.members { 58 | let name = str_to_ident(name); 59 | names.push(quote!(#name)); 60 | 61 | let ty = str_to_type(&abi_type.to_rust_type_path()); 62 | 63 | // Tuples type used as rust type item path must be surrounded 64 | // by angle brackets. 65 | let ty_punctuated = match abi_type { 66 | AbiTypeAny::Tuple(_) => quote!(<#ty>), 67 | _ => quote!(#ty), 68 | }; 69 | 70 | if is_first { 71 | sizes.push(quote!(#ty_punctuated::serialized_size(&__rust.#name))); 72 | is_first = false; 73 | } else { 74 | sizes.push(quote!(+ #ty_punctuated::serialized_size(&__rust.#name))); 75 | } 76 | 77 | sers.push(quote!(__out.extend(#ty_punctuated::serialize(&__rust.#name));)); 78 | 79 | desers.push(quote! { 80 | let #name = #ty_punctuated::deserialize(__felts, __offset)?; 81 | __offset += #ty_punctuated::serialized_size(&#name); 82 | }); 83 | } 84 | 85 | let gentys: Vec = self.get_gentys().iter().map(|g| str_to_ident(g)).collect(); 86 | 87 | let impl_line = if self.is_generic() { 88 | generic::impl_with_gentys_tokens(&struct_name, &gentys) 89 | } else { 90 | quote!(impl starknet_abigen_parser::CairoType for #struct_name) 91 | }; 92 | 93 | let rust_type = if self.is_generic() { 94 | generic::rust_associated_type_gentys_tokens(&struct_name, &gentys) 95 | } else { 96 | quote!( 97 | type RustType = Self; 98 | ) 99 | }; 100 | 101 | quote! { 102 | #impl_line { 103 | 104 | #rust_type 105 | 106 | const SERIALIZED_SIZE: std::option::Option = None; 107 | 108 | #[inline] 109 | fn serialized_size(__rust: &Self::RustType) -> usize { 110 | #(#sizes) * 111 | } 112 | 113 | fn serialize(__rust: &Self::RustType) -> Vec { 114 | let mut __out: Vec = vec![]; 115 | #(#sers)* 116 | __out 117 | } 118 | 119 | fn deserialize(__felts: &[starknet::core::types::FieldElement], __offset: usize) -> starknet_abigen_parser::cairo_types::Result { 120 | let mut __offset = __offset; 121 | #(#desers)* 122 | Ok(#struct_name { 123 | #(#names),* 124 | }) 125 | } 126 | } 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /crates/macros/src/abigen/expand/utils.rs: -------------------------------------------------------------------------------- 1 | //! Utils function for expansion. 2 | use syn::{Ident, LitStr, Type}; 3 | 4 | /// 5 | pub fn str_to_ident(str_in: &str) -> Ident { 6 | Ident::new(str_in, proc_macro2::Span::call_site()) 7 | } 8 | 9 | /// 10 | pub fn str_to_type(str_in: &str) -> Type { 11 | syn::parse_str(str_in).unwrap_or_else(|_| panic!("Can't convert {} to syn::Type", str_in)) 12 | } 13 | 14 | /// 15 | pub fn str_to_litstr(str_in: &str) -> LitStr { 16 | LitStr::new(str_in, proc_macro2::Span::call_site()) 17 | } 18 | -------------------------------------------------------------------------------- /crates/macros/src/abigen/mod.rs: -------------------------------------------------------------------------------- 1 | //! This crate contains all the logic to expand the parsed ABI types into 2 | //! rust code. 3 | //! 4 | //! Important note, functions can't be generic when they are entry point 5 | //! of a Cairo contracts. 6 | //! For this reason, all the generic types are handles for structs and enums 7 | //! generation only, and then applied on functions inputs/output. 8 | //! 9 | //! As the ABI as everything flatten, we must ensure that structs and enums are 10 | //! checked for genericty to avoid duplicated types and detect correctly 11 | //! the members/variants that are generic. 12 | use proc_macro::TokenStream; 13 | use proc_macro2::TokenStream as TokenStream2; 14 | use quote::quote; 15 | use syn::parse_macro_input; 16 | 17 | use std::collections::HashMap; 18 | 19 | use starknet::core::types::contract::{AbiEntry, StateMutability}; 20 | use starknet_abigen_parser::cairo_types::{CAIRO_BASIC_ENUMS, CAIRO_BASIC_STRUCTS}; 21 | use starknet_abigen_parser::{CairoEnum, CairoEvent, CairoFunction, CairoStruct}; 22 | 23 | mod expand; 24 | use expand::contract::CairoContract; 25 | use expand::{Expandable, ExpandableEvent}; 26 | 27 | mod contract_abi; 28 | use contract_abi::ContractAbi; 29 | 30 | use crate::abigen::expand::utils; 31 | 32 | pub fn abigen_internal(input: TokenStream) -> TokenStream { 33 | let contract_abi = parse_macro_input!(input as ContractAbi); 34 | let contract_name = contract_abi.name; 35 | let abi = contract_abi.abi; 36 | 37 | let mut tokens: Vec = vec![]; 38 | 39 | tokens.push(CairoContract::expand(contract_name.clone())); 40 | 41 | let mut structs: HashMap = HashMap::new(); 42 | let mut enums: HashMap = HashMap::new(); 43 | let mut views = vec![]; 44 | let mut externals = vec![]; 45 | let mut events = vec![]; 46 | 47 | for entry in &abi { 48 | parse_entry( 49 | entry, 50 | &mut structs, 51 | &mut enums, 52 | &mut externals, 53 | &mut views, 54 | &mut events, 55 | ); 56 | } 57 | 58 | for (_, cs) in structs { 59 | tokens.push(cs.expand_decl()); 60 | tokens.push(cs.expand_impl()); 61 | } 62 | 63 | for (_, ce) in enums { 64 | tokens.push(ce.expand_decl()); 65 | tokens.push(ce.expand_impl()); 66 | } 67 | 68 | for ev in &events { 69 | tokens.push(ev.expand_decl()); 70 | tokens.push(ev.expand_impl(&events)); 71 | } 72 | 73 | let reader = utils::str_to_ident(format!("{}Reader", contract_name).as_str()); 74 | tokens.push(quote! { 75 | impl #contract_name { 76 | #(#externals)* 77 | } 78 | 79 | impl<'a, P: starknet::providers::Provider + Sync> #reader<'a, P> { 80 | #(#views)* 81 | } 82 | }); 83 | 84 | let expanded = quote! { 85 | #(#tokens)* 86 | }; 87 | 88 | expanded.into() 89 | } 90 | 91 | fn parse_entry( 92 | entry: &AbiEntry, 93 | structs: &mut HashMap, 94 | enums: &mut HashMap, 95 | externals: &mut Vec, 96 | views: &mut Vec, 97 | events: &mut Vec, 98 | ) { 99 | match entry { 100 | AbiEntry::Struct(s) => { 101 | let cs = CairoStruct::new(&s.name, &s.members); 102 | 103 | if CAIRO_BASIC_STRUCTS.contains(&cs.get_name().as_str()) { 104 | return; 105 | } 106 | 107 | if let Some(ref mut existing_cs) = structs.get_mut(&cs.get_name()) { 108 | cs.compare_generic_types(existing_cs); 109 | } else { 110 | structs.insert(cs.get_name(), cs.clone()); 111 | } 112 | } 113 | AbiEntry::Enum(e) => { 114 | let ce = CairoEnum::new(&e.name, &e.variants); 115 | 116 | if CAIRO_BASIC_ENUMS.contains(&ce.get_name().as_str()) { 117 | return; 118 | } 119 | 120 | if let Some(ref mut existing_ce) = enums.get_mut(&ce.get_name()) { 121 | ce.compare_generic_types(existing_ce); 122 | } else { 123 | enums.insert(ce.get_name(), ce.clone()); 124 | } 125 | } 126 | AbiEntry::Function(f) => { 127 | // Functions cannot be generic when they are entry point. 128 | // From this statement, we can safely assume that any function name is 129 | // unique. 130 | let cf = CairoFunction::new(&f.name, f.state_mutability.clone(), &f.inputs, &f.outputs); 131 | match f.state_mutability { 132 | StateMutability::View => views.push(cf.expand_impl()), 133 | StateMutability::External => externals.push(cf.expand_impl()), 134 | } 135 | } 136 | AbiEntry::Event(ev) => { 137 | if let Some(cev) = CairoEvent::new(ev) { 138 | events.push(cev); 139 | } 140 | } 141 | AbiEntry::Interface(interface) => { 142 | for entry in &interface.items { 143 | parse_entry(entry, structs, enums, externals, views, events); 144 | } 145 | } 146 | _ => (), 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /crates/macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | 3 | mod abigen; 4 | use abigen::abigen_internal; 5 | 6 | #[proc_macro] 7 | pub fn abigen(input: TokenStream) -> TokenStream { 8 | abigen_internal(input) 9 | } 10 | -------------------------------------------------------------------------------- /crates/parser/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "starknet-abigen-parser" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | starknet.workspace = true 8 | thiserror.workspace = true 9 | -------------------------------------------------------------------------------- /crates/parser/src/abi_types/array.rs: -------------------------------------------------------------------------------- 1 | use super::{AbiType, AbiTypeAny, GENTY_FROZEN}; 2 | 3 | #[derive(Debug, PartialEq, Clone)] 4 | pub struct AbiArray { 5 | pub cairo_type: String, 6 | pub genty: String, 7 | pub inner: Box, 8 | } 9 | 10 | impl AbiArray { 11 | pub fn new(cairo_type: &str, inner: AbiTypeAny) -> Self { 12 | AbiArray { 13 | cairo_type: cairo_type.to_string(), 14 | genty: String::new(), 15 | inner: Box::new(inner), 16 | } 17 | } 18 | } 19 | 20 | impl AbiType for AbiArray { 21 | fn get_genty(&self) -> String { 22 | self.genty.clone() 23 | } 24 | 25 | fn compare_generic(&mut self, other: &AbiTypeAny) { 26 | match other { 27 | AbiTypeAny::Array(_) => { 28 | if self.genty != GENTY_FROZEN { 29 | self.genty = other.get_genty(); 30 | } 31 | } 32 | _ => { 33 | self.inner.compare_generic(other); 34 | } 35 | }; 36 | } 37 | 38 | fn apply_generic(&mut self, cairo_types_gentys: Vec<(&str, &str)>) -> (String, bool) { 39 | // Check if the whole array is the generic. 40 | for (cairo_type, genty) in &cairo_types_gentys { 41 | if &self.get_cairo_type_full() == cairo_type { 42 | self.genty = genty.to_string(); 43 | return (genty.to_string(), true); 44 | } 45 | } 46 | 47 | let (gen_str, is_generic) = self.inner.apply_generic(cairo_types_gentys); 48 | ( 49 | format!("{}::<{}>", self.cairo_type.clone(), &gen_str), 50 | is_generic, 51 | ) 52 | } 53 | 54 | fn get_cairo_type_full(&self) -> String { 55 | format!( 56 | "{}::<{}>", 57 | self.cairo_type.clone(), 58 | &self.inner.get_cairo_type_full() 59 | ) 60 | } 61 | 62 | fn get_cairo_type_name(&self) -> String { 63 | self.cairo_type 64 | .split("::") 65 | .last() 66 | .unwrap_or(&self.cairo_type) 67 | .to_string() 68 | } 69 | 70 | fn to_rust_type(&self) -> String { 71 | if !self.genty.is_empty() && self.genty != GENTY_FROZEN { 72 | self.genty.clone() 73 | } else { 74 | format!("Vec<{}>", &self.inner.to_rust_type()) 75 | } 76 | } 77 | 78 | fn to_rust_type_path(&self) -> String { 79 | if !self.genty.is_empty() && self.genty != GENTY_FROZEN { 80 | self.genty.clone() 81 | } else { 82 | format!("Vec::<{}>", &self.inner.to_rust_type()) 83 | } 84 | } 85 | } 86 | 87 | #[cfg(test)] 88 | mod tests { 89 | use super::*; 90 | use crate::abi_types::AbiTypeAny; 91 | 92 | fn get_default() -> AbiArray { 93 | AbiArray::new( 94 | "core::array::Array", 95 | AbiTypeAny::Basic("core::felt252".into()), 96 | ) 97 | } 98 | 99 | #[test] 100 | fn get_cairo_type_full() { 101 | let t = get_default(); 102 | assert_eq!( 103 | t.get_cairo_type_full(), 104 | "core::array::Array::" 105 | ); 106 | } 107 | 108 | #[test] 109 | fn cairo_type_name_only() { 110 | let t = get_default(); 111 | assert_eq!(t.get_cairo_type_name(), "Array"); 112 | } 113 | 114 | #[test] 115 | fn to_rust_type() { 116 | let t = get_default(); 117 | assert_eq!(t.to_rust_type(), "Vec"); 118 | } 119 | 120 | #[test] 121 | fn to_rust_type_path() { 122 | let t = get_default(); 123 | assert_eq!( 124 | t.to_rust_type_path(), 125 | "Vec::" 126 | ); 127 | } 128 | 129 | #[test] 130 | fn from_string() { 131 | let t = AbiTypeAny::from_string("core::array::Array::"); 132 | assert_eq!(t, AbiTypeAny::Array(get_default())); 133 | } 134 | 135 | #[test] 136 | fn from_string_array_tuple() { 137 | let t = 138 | AbiTypeAny::from_string("core::array::Array::<(core::felt252, core::integer::u32)>"); 139 | assert_eq!( 140 | t, 141 | AbiTypeAny::Array(AbiArray::new( 142 | "core::array::Array", 143 | AbiTypeAny::Tuple( 144 | vec![ 145 | AbiTypeAny::Basic("core::felt252".into()), 146 | AbiTypeAny::Basic("core::integer::u32".into()), 147 | ] 148 | .into() 149 | ) 150 | )) 151 | ); 152 | } 153 | 154 | #[test] 155 | fn generic_array() { 156 | let mut t = AbiTypeAny::from_string("core::array::Array::"); 157 | assert_eq!( 158 | t.apply_generic(vec![("core::array::Array::", "A")]), 159 | ("A".to_string(), true) 160 | ); 161 | } 162 | 163 | #[test] 164 | fn generic_inner() { 165 | let mut t = AbiTypeAny::from_string("core::array::Array::"); 166 | assert_eq!( 167 | t.apply_generic(vec![("core::felt252", "A")]), 168 | ("core::array::Array::".to_string(), true) 169 | ); 170 | } 171 | 172 | #[test] 173 | fn generic_not() { 174 | let mut t = AbiTypeAny::from_string("core::array::Array::"); 175 | assert_eq!( 176 | t.apply_generic(vec![("core::array::Array", "A")]), 177 | ("core::array::Array::".to_string(), false) 178 | ); 179 | } 180 | 181 | #[test] 182 | fn generic_not_inner() { 183 | let mut t = AbiTypeAny::from_string("core::array::Array::"); 184 | assert_eq!( 185 | t.apply_generic(vec![("core::felt252", "A")]), 186 | ("core::array::Array::".to_string(), false) 187 | ); 188 | } 189 | 190 | #[test] 191 | fn array_in_array_not_generic() { 192 | let t = AbiTypeAny::from_string("core::array::Span::>"); 193 | assert_eq!( 194 | t.to_rust_type(), 195 | "Vec>" 196 | ); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /crates/parser/src/abi_types/basic.rs: -------------------------------------------------------------------------------- 1 | //! Basic types are all cairo types that are not Array/Span, 2 | //! generic Struct/Enum or tuple. 3 | //! 4 | //! To support recursion, the basic type stored the generic type 5 | //! that is assigned to it, if it belongs to a generic struct/enum. 6 | use super::{AbiType, AbiTypeAny, GENTY_FROZEN}; 7 | use crate::cairo_types::CAIRO_TYPES_PATH; 8 | 9 | #[derive(Debug, PartialEq, Clone)] 10 | pub struct AbiBasic { 11 | cairo_type: String, 12 | genty: String, 13 | } 14 | 15 | impl AbiBasic { 16 | /// Initializes a new instance. 17 | pub fn new(cairo_type: &str) -> Self { 18 | AbiBasic { 19 | cairo_type: cairo_type.to_string(), 20 | genty: String::new(), 21 | } 22 | } 23 | 24 | /// Maps a basic type to a built-in type that may already contains 25 | /// a `CairoType` implementation. If not, it's the name of the type itself. 26 | fn to_rust_or_cairo_builtin_type(&self) -> String { 27 | let s = self.get_cairo_type_name(); 28 | match s.as_str() { 29 | "felt252" => "starknet::core::types::FieldElement".to_string(), 30 | "ContractAddress" => { 31 | format!("{}::ContractAddress", CAIRO_TYPES_PATH) 32 | } 33 | "ClassHash" => format!("{}::ClassHash", CAIRO_TYPES_PATH), 34 | "EthAddress" => format!("{}::EthAddress", CAIRO_TYPES_PATH), 35 | _ => s.clone(), 36 | } 37 | } 38 | } 39 | 40 | impl From<&str> for AbiBasic { 41 | fn from(s: &str) -> Self { 42 | Self::new(s) 43 | } 44 | } 45 | 46 | impl From<&String> for AbiBasic { 47 | fn from(s: &String) -> Self { 48 | Self::new(s) 49 | } 50 | } 51 | 52 | impl AbiType for AbiBasic { 53 | fn get_genty(&self) -> String { 54 | self.genty.clone() 55 | } 56 | 57 | fn compare_generic(&mut self, other: &AbiTypeAny) { 58 | if self.genty != GENTY_FROZEN { 59 | self.genty = other.get_genty(); 60 | } 61 | } 62 | 63 | fn apply_generic(&mut self, cairo_types_gentys: Vec<(&str, &str)>) -> (String, bool) { 64 | // A basic type can only match one of the given types. 65 | // It will return the first match we can find, if any. 66 | for (cairo_type, genty) in cairo_types_gentys { 67 | if self.cairo_type == cairo_type { 68 | self.genty = genty.to_string(); 69 | return (genty.to_string(), true); 70 | } 71 | } 72 | 73 | self.genty = GENTY_FROZEN.to_string(); 74 | (self.cairo_type.clone(), false) 75 | } 76 | 77 | fn get_cairo_type_full(&self) -> String { 78 | self.cairo_type.clone() 79 | } 80 | 81 | fn get_cairo_type_name(&self) -> String { 82 | self.cairo_type 83 | .split("::") 84 | .last() 85 | .unwrap_or(&self.cairo_type) 86 | .to_string() 87 | } 88 | 89 | fn to_rust_type(&self) -> String { 90 | if !self.genty.is_empty() && self.genty != GENTY_FROZEN { 91 | self.genty.clone() 92 | } else { 93 | self.to_rust_or_cairo_builtin_type() 94 | } 95 | } 96 | 97 | fn to_rust_type_path(&self) -> String { 98 | self.to_rust_type() 99 | } 100 | } 101 | 102 | #[cfg(test)] 103 | mod tests { 104 | use super::*; 105 | use crate::abi_types::AbiTypeAny; 106 | 107 | fn get_default() -> AbiBasic { 108 | AbiBasic::new("core::felt252") 109 | } 110 | 111 | #[test] 112 | fn get_cairo_type_full() { 113 | let t = get_default(); 114 | assert_eq!(t.get_cairo_type_full(), "core::felt252"); 115 | } 116 | 117 | #[test] 118 | fn cairo_type_name_only() { 119 | let t = get_default(); 120 | assert_eq!(t.get_cairo_type_name(), "felt252"); 121 | } 122 | 123 | #[test] 124 | fn to_rust_type() { 125 | let t = get_default(); 126 | assert_eq!(t.to_rust_type(), "starknet::core::types::FieldElement"); 127 | } 128 | 129 | #[test] 130 | fn to_rust_type_path() { 131 | let t = get_default(); 132 | assert_eq!(t.to_rust_type_path(), "starknet::core::types::FieldElement"); 133 | } 134 | // TODO: add more tests for other built-in types. 135 | 136 | #[test] 137 | fn from_string() { 138 | let t = AbiTypeAny::from_string("core::felt252"); 139 | assert_eq!(t, AbiTypeAny::Basic("core::felt252".into())); 140 | } 141 | 142 | #[test] 143 | fn from_string_generic() { 144 | let mut t = AbiTypeAny::from_string("core::felt252"); 145 | assert_eq!( 146 | t.apply_generic(vec![("core::felt252", "A")]), 147 | ("A".to_string(), true) 148 | ); 149 | } 150 | 151 | #[test] 152 | fn from_string_not_generic() { 153 | let mut t = AbiTypeAny::from_string("core::u32"); 154 | assert_eq!( 155 | t.apply_generic(vec![("core::felt252", "A")]), 156 | ("core::u32".to_string(), false) 157 | ); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /crates/parser/src/abi_types/generic.rs: -------------------------------------------------------------------------------- 1 | use super::{AbiType, AbiTypeAny, GENTY_FROZEN}; 2 | 3 | #[derive(Debug, PartialEq, Clone)] 4 | pub struct AbiGeneric { 5 | pub cairo_type: String, 6 | pub genty: String, 7 | pub inners: Vec, 8 | } 9 | 10 | impl AbiGeneric { 11 | /// Initializes a new instance. 12 | pub fn new(cairo_type: &str, inners: Vec) -> Self { 13 | AbiGeneric { 14 | cairo_type: cairo_type.to_string(), 15 | genty: String::new(), 16 | inners, 17 | } 18 | } 19 | 20 | /// Gets the definition of the type with it's generic types. 21 | pub fn get_rust_generic_def(&self, suffix: &str) -> String { 22 | let gentys = self.get_gentys_only(); 23 | format!( 24 | "{}<{}{}>", 25 | self.get_cairo_type_name(), 26 | gentys.join(", "), 27 | suffix 28 | ) 29 | } 30 | 31 | /// Returns only the generic types list. 32 | pub fn get_gentys_only(&self) -> Vec { 33 | // Starts to 'A'. 34 | let ascii: u8 = 65; 35 | 36 | let mut gentys = vec![]; 37 | for (i, _) in self.inners.iter().enumerate() { 38 | gentys.push(((ascii + i as u8) as char).to_string()); 39 | } 40 | 41 | gentys 42 | } 43 | 44 | /// Returns the list of tuple, containing the (cairo_type, generic_type) 45 | /// for each generic type. 46 | pub fn get_cairo_types_gentys(&self) -> Vec<(String, String)> { 47 | // Starts to 'A'. 48 | let ascii: u8 = 65; 49 | 50 | let mut cairo_types_gentys = vec![]; 51 | for (i, inner) in self.inners.iter().enumerate() { 52 | let genty = ((ascii + i as u8) as char).to_string(); 53 | cairo_types_gentys.push((inner.get_cairo_type_full(), genty)); 54 | } 55 | 56 | cairo_types_gentys 57 | } 58 | } 59 | 60 | impl AbiType for AbiGeneric { 61 | fn get_genty(&self) -> String { 62 | self.genty.clone() 63 | } 64 | 65 | fn compare_generic(&mut self, other: &AbiTypeAny) { 66 | match other { 67 | AbiTypeAny::Generic(_) => { 68 | if self.genty != GENTY_FROZEN { 69 | self.genty = other.get_genty(); 70 | } 71 | } 72 | _ => { 73 | for inner in &mut self.inners { 74 | inner.compare_generic(other); 75 | } 76 | } 77 | }; 78 | } 79 | 80 | fn apply_generic(&mut self, cairo_types_gentys: Vec<(&str, &str)>) -> (String, bool) { 81 | // Check if the whole struct is the generic. 82 | for (cairo_type, genty) in &cairo_types_gentys { 83 | if &self.get_cairo_type_full() == cairo_type { 84 | self.genty = genty.to_string(); 85 | return (genty.to_string(), true); 86 | } 87 | } 88 | 89 | let mut struct_has_generic = false; 90 | let mut s = format!("{}::<", self.cairo_type); 91 | let arr_len = self.inners.len(); 92 | 93 | for (idx, inner) in self.inners.iter_mut().enumerate() { 94 | let (type_str, is_generic) = inner.apply_generic(cairo_types_gentys.clone()); 95 | 96 | if is_generic && !struct_has_generic { 97 | struct_has_generic = true; 98 | } 99 | 100 | s.push_str(&type_str); 101 | 102 | if idx < arr_len - 1 { 103 | s.push_str(", "); 104 | } 105 | } 106 | s.push('>'); 107 | 108 | (s, struct_has_generic) 109 | } 110 | 111 | fn get_cairo_type_full(&self) -> String { 112 | let mut s = format!("{}::<", self.cairo_type); 113 | 114 | for (idx, inner) in self.inners.iter().enumerate() { 115 | s.push_str(&inner.get_cairo_type_full()); 116 | 117 | if idx < self.inners.len() - 1 { 118 | s.push_str(", "); 119 | } 120 | } 121 | s.push('>'); 122 | s 123 | } 124 | 125 | fn get_cairo_type_name(&self) -> String { 126 | // TODO: need to opti that with regex? 127 | let f = self 128 | .cairo_type 129 | .split('<') 130 | .nth(0) 131 | .unwrap_or(&self.cairo_type) 132 | .to_string(); 133 | f.split("::").last().unwrap_or(&f).to_string() 134 | } 135 | 136 | fn to_rust_type(&self) -> String { 137 | if !self.genty.is_empty() && self.genty != GENTY_FROZEN { 138 | self.genty.clone() 139 | } else { 140 | let joined_inners = self 141 | .inners 142 | .iter() 143 | .map(|i| i.to_rust_type()) 144 | .collect::>() 145 | .join(", "); 146 | 147 | format!("{}<{}>", self.get_cairo_type_name(), joined_inners) 148 | } 149 | } 150 | 151 | fn to_rust_type_path(&self) -> String { 152 | if !self.genty.is_empty() && self.genty != GENTY_FROZEN { 153 | self.genty.clone() 154 | } else { 155 | let joined_inners = self 156 | .inners 157 | .iter() 158 | .map(|i| i.to_rust_type()) 159 | .collect::>() 160 | .join(", "); 161 | 162 | format!("{}::<{}>", self.get_cairo_type_name(), joined_inners) 163 | } 164 | } 165 | } 166 | 167 | #[cfg(test)] 168 | mod tests { 169 | use super::*; 170 | use crate::abi_types::{AbiArray, AbiTypeAny}; 171 | 172 | fn get_default() -> AbiGeneric { 173 | AbiGeneric::new( 174 | "contract1::MyStruct", 175 | vec![AbiTypeAny::Basic("core::felt252".into())], 176 | ) 177 | } 178 | 179 | fn get_default_multiple() -> AbiGeneric { 180 | AbiGeneric::new( 181 | "contract1::MyStruct", 182 | vec![ 183 | AbiTypeAny::Basic("core::felt252".into()), 184 | AbiTypeAny::Basic("core::integer::u32".into()), 185 | ], 186 | ) 187 | } 188 | 189 | #[test] 190 | fn cairo_type() { 191 | let t = get_default(); 192 | assert_eq!(t.cairo_type, "contract1::MyStruct"); 193 | } 194 | 195 | #[test] 196 | fn get_cairo_type_full() { 197 | let t = get_default(); 198 | assert_eq!( 199 | t.get_cairo_type_full(), 200 | "contract1::MyStruct::" 201 | ); 202 | } 203 | 204 | #[test] 205 | fn cairo_type_name_only() { 206 | let t = get_default(); 207 | assert_eq!(t.get_cairo_type_name(), "MyStruct"); 208 | } 209 | 210 | #[test] 211 | fn to_rust_type() { 212 | let t = get_default(); 213 | assert_eq!( 214 | t.to_rust_type(), 215 | "MyStruct" 216 | ); 217 | } 218 | 219 | #[test] 220 | fn to_rust_type_path() { 221 | let t = get_default(); 222 | assert_eq!( 223 | t.to_rust_type_path(), 224 | "MyStruct::" 225 | ); 226 | } 227 | 228 | #[test] 229 | fn from_string() { 230 | let t = AbiTypeAny::from_string("contract1::MyStruct::"); 231 | assert_eq!(t, AbiTypeAny::Generic(get_default())); 232 | } 233 | 234 | #[test] 235 | fn from_string_array_tuple() { 236 | let t = AbiTypeAny::from_string("contract1::MyStruct::, (core::felt252, core::integer::u32)>"); 237 | assert_eq!( 238 | t, 239 | AbiTypeAny::Generic(AbiGeneric::new( 240 | "contract1::MyStruct", 241 | vec![ 242 | AbiTypeAny::Array(AbiArray::new( 243 | "core::array::Array", 244 | AbiTypeAny::Basic("core::felt252".into()) 245 | )), 246 | AbiTypeAny::Tuple( 247 | vec![ 248 | AbiTypeAny::Basic("core::felt252".into()), 249 | AbiTypeAny::Basic("core::integer::u32".into()), 250 | ] 251 | .into() 252 | ) 253 | ] 254 | )) 255 | ); 256 | } 257 | 258 | #[test] 259 | fn get_cairo_type_full_multiple() { 260 | let t = get_default_multiple(); 261 | assert_eq!( 262 | t.get_cairo_type_full(), 263 | "contract1::MyStruct::" 264 | ); 265 | } 266 | 267 | #[test] 268 | fn to_rust_type_multiple() { 269 | let t = get_default_multiple(); 270 | assert_eq!( 271 | t.to_rust_type(), 272 | "MyStruct" 273 | ); 274 | } 275 | 276 | #[test] 277 | fn to_rust_type_path_multiple() { 278 | let t = get_default_multiple(); 279 | assert_eq!( 280 | t.to_rust_type_path(), 281 | "MyStruct::" 282 | ); 283 | } 284 | 285 | #[test] 286 | fn from_string_multiple() { 287 | let t = AbiTypeAny::from_string("contract1::MyStruct::"); 288 | assert_eq!(t, AbiTypeAny::Generic(get_default_multiple())); 289 | } 290 | 291 | #[test] 292 | fn generic_generic() { 293 | let mut t = AbiTypeAny::from_string("contract1::MyStruct::"); 294 | assert_eq!( 295 | t.apply_generic(vec![("contract1::MyStruct::", "A")]), 296 | ("A".to_string(), true) 297 | ); 298 | } 299 | 300 | #[test] 301 | fn generic_inner() { 302 | let mut t = AbiTypeAny::from_string("contract1::MyStruct::"); 303 | assert_eq!( 304 | t.apply_generic(vec![("core::felt252", "A")]), 305 | ("contract1::MyStruct::".to_string(), true) 306 | ); 307 | } 308 | 309 | #[test] 310 | fn generic_generic_multiple() { 311 | let mut t = 312 | AbiTypeAny::from_string("contract1::MyStruct::"); 313 | assert_eq!( 314 | t.apply_generic(vec![( 315 | "contract1::MyStruct::", 316 | "A" 317 | )]), 318 | ("A".to_string(), true) 319 | ); 320 | } 321 | 322 | #[test] 323 | fn generic_inner_multiple() { 324 | let mut t = 325 | AbiTypeAny::from_string("contract1::MyStruct::"); 326 | assert_eq!( 327 | t.apply_generic(vec![("core::integer::u32", "A")]), 328 | ("contract1::MyStruct::".to_string(), true) 329 | ); 330 | } 331 | 332 | #[test] 333 | fn generic_inner_multiple_array() { 334 | let mut t = AbiTypeAny::from_string( 335 | "contract1::MyStruct::, core::integer::u32>", 336 | ); 337 | assert_eq!( 338 | t.apply_generic(vec![("core::felt252", "A")]), 339 | ( 340 | "contract1::MyStruct::, core::integer::u32>".to_string(), 341 | true 342 | ) 343 | ); 344 | } 345 | 346 | #[test] 347 | fn generic_inner_multiple_ab() { 348 | let mut t = 349 | AbiTypeAny::from_string("contract1::MyStruct::"); 350 | assert_eq!( 351 | t.apply_generic(vec![("core::felt252", "A"), ("core::integer::u32", "B")]), 352 | ("contract1::MyStruct::".to_string(), true) 353 | ); 354 | } 355 | } 356 | -------------------------------------------------------------------------------- /crates/parser/src/abi_types/mod.rs: -------------------------------------------------------------------------------- 1 | //! ABI types base module. 2 | //! 3 | //! The idea of those types is to handle the parsing of any valid 4 | //! flatten cairo type that can also contain nested types. 5 | use std::iter::Peekable; 6 | use std::str::Chars; 7 | 8 | pub mod basic; 9 | pub use basic::AbiBasic; 10 | 11 | pub mod array; 12 | pub use array::AbiArray; 13 | 14 | pub mod generic; 15 | pub use generic::AbiGeneric; 16 | 17 | pub mod tuple; 18 | pub use tuple::AbiTuple; 19 | 20 | /// If a generic type is flagged as frozen, it means 21 | /// that at least one type in the ABI were found 22 | /// different from the generic one. In that case, we 23 | /// don't want it to be modified again, even if it matches 24 | /// an other generic type. 25 | /// 26 | /// # Example 27 | /// 28 | /// struct MyStruct { 29 | /// a: felt252, 30 | /// b: T, 31 | /// } 32 | /// 33 | /// In this scenario, when `MyStruct` is used, 34 | /// we can't know which of a or b is generic. So both will be which is fine. 35 | /// But if in the same ABI we have `MyStruct`, then it will 36 | /// be possible to detect that b is generic, a is not. 37 | /// So if we parse `MyStruct` first, we do want to FREEZE a 38 | /// as NOT being generic. Like this even if for `MyStruct` there is 39 | /// a match, it will be ignored. 40 | const GENTY_FROZEN: &str = "_"; 41 | 42 | #[derive(Debug, PartialEq, Clone)] 43 | pub enum AbiTypeAny { 44 | Basic(AbiBasic), 45 | Array(AbiArray), 46 | // Generics is for struct and enums. 47 | Generic(AbiGeneric), 48 | Tuple(AbiTuple), 49 | } 50 | 51 | pub trait AbiType { 52 | /// Gets the generic type if the type is generic, 53 | /// the type name otherwise. 54 | fn get_genty(&self) -> String; 55 | 56 | /// Compares the generic state between two `AbiTypeAny`. 57 | /// As the ABI does not provide information about the type genericity, 58 | /// we must compare several types with the same name to successfully identify 59 | /// the one that are generic. 60 | fn compare_generic(&mut self, other: &AbiTypeAny); 61 | 62 | /// Applies a generic type for the given cairo type Vec(cairo_type, generic_type). 63 | /// Returns the generic type applied and true if the type is generic, 64 | /// false and the type itself otherwise. 65 | fn apply_generic(&mut self, cairo_types_gentys: Vec<(&str, &str)>) -> (String, bool); 66 | 67 | /// Gets the full cairo type. A "full" type includes the type 68 | /// and possible nested types. 69 | fn get_cairo_type_full(&self) -> String; 70 | 71 | /// Returns only the cairo type name. 72 | fn get_cairo_type_name(&self) -> String; 73 | 74 | /// Gets the rust type from the `AbiType`. 75 | /// This always includes all possible nested types and their genericity. 76 | fn to_rust_type(&self) -> String; 77 | 78 | /// Get the rust type item path from the `AbiType`. 79 | /// This always includes all possible nested types and their genericity. 80 | fn to_rust_type_path(&self) -> String; 81 | } 82 | 83 | impl AbiType for AbiTypeAny { 84 | fn compare_generic(&mut self, other: &AbiTypeAny) { 85 | match self { 86 | AbiTypeAny::Basic(a) => a.compare_generic(other), 87 | AbiTypeAny::Array(a) => a.compare_generic(other), 88 | AbiTypeAny::Generic(a) => a.compare_generic(other), 89 | AbiTypeAny::Tuple(a) => a.compare_generic(other), 90 | } 91 | } 92 | 93 | fn get_genty(&self) -> String { 94 | match self { 95 | AbiTypeAny::Basic(a) => a.get_genty(), 96 | AbiTypeAny::Array(a) => a.get_genty(), 97 | AbiTypeAny::Generic(a) => a.get_genty(), 98 | AbiTypeAny::Tuple(a) => a.get_genty(), 99 | } 100 | } 101 | 102 | fn apply_generic(&mut self, cairo_types_gentys: Vec<(&str, &str)>) -> (String, bool) { 103 | match self { 104 | AbiTypeAny::Basic(a) => a.apply_generic(cairo_types_gentys), 105 | AbiTypeAny::Array(a) => a.apply_generic(cairo_types_gentys), 106 | AbiTypeAny::Generic(a) => a.apply_generic(cairo_types_gentys), 107 | AbiTypeAny::Tuple(a) => a.apply_generic(cairo_types_gentys), 108 | } 109 | } 110 | 111 | fn get_cairo_type_full(&self) -> String { 112 | match self { 113 | AbiTypeAny::Basic(a) => a.get_cairo_type_full(), 114 | AbiTypeAny::Array(a) => a.get_cairo_type_full(), 115 | AbiTypeAny::Generic(a) => a.get_cairo_type_full(), 116 | AbiTypeAny::Tuple(a) => a.get_cairo_type_full(), 117 | } 118 | } 119 | 120 | fn get_cairo_type_name(&self) -> String { 121 | match self { 122 | AbiTypeAny::Basic(a) => a.get_cairo_type_name(), 123 | AbiTypeAny::Array(a) => a.get_cairo_type_name(), 124 | AbiTypeAny::Generic(a) => a.get_cairo_type_name(), 125 | AbiTypeAny::Tuple(a) => a.get_cairo_type_name(), 126 | } 127 | } 128 | 129 | fn to_rust_type(&self) -> String { 130 | match self { 131 | AbiTypeAny::Basic(a) => a.to_rust_type(), 132 | AbiTypeAny::Array(a) => a.to_rust_type(), 133 | AbiTypeAny::Generic(a) => a.to_rust_type(), 134 | AbiTypeAny::Tuple(a) => a.to_rust_type(), 135 | } 136 | } 137 | 138 | fn to_rust_type_path(&self) -> String { 139 | match self { 140 | AbiTypeAny::Basic(a) => a.to_rust_type_path(), 141 | AbiTypeAny::Array(a) => a.to_rust_type_path(), 142 | AbiTypeAny::Generic(a) => a.to_rust_type_path(), 143 | AbiTypeAny::Tuple(a) => a.to_rust_type_path(), 144 | } 145 | } 146 | } 147 | 148 | /// Utils functions for `AbiTypeAny` to be called 149 | /// without testing the enum variant. 150 | impl AbiTypeAny { 151 | /// Returns true if the type is a generic, 152 | /// false otherwise. 153 | pub fn is_generic(&self) -> bool { 154 | matches!(self, Self::Generic(_)) 155 | } 156 | 157 | /// Returns true if the type is the unit type, 158 | /// false otherwise. 159 | pub fn is_unit(&self) -> bool { 160 | match self { 161 | Self::Basic(b) => b.get_cairo_type_full() == "()", 162 | _ => false, 163 | } 164 | } 165 | 166 | /// Parses a string to build an `AbiTypeAny`. 167 | pub fn from_string(type_string: &str) -> Self { 168 | let mut chars = type_string.chars().peekable(); 169 | Self::parse_type(&mut chars) 170 | } 171 | 172 | /// Parses any cairo type from the given string. 173 | /// This function handles the possible nested types. 174 | fn parse_type(chars: &mut Peekable) -> Self { 175 | let mut generic_types = Vec::new(); 176 | let mut current_type = String::new(); 177 | 178 | while let Some(c) = chars.peek() { 179 | match c { 180 | '<' => { 181 | chars.next(); 182 | // In cairo, a generic type is always preceeded by a separator "::". 183 | let generic_type = 184 | Self::parse_generic(current_type.trim_end_matches("::"), chars); 185 | generic_types.push(generic_type); 186 | current_type.clear(); 187 | } 188 | '>' => { 189 | break; 190 | } 191 | '(' => { 192 | chars.next(); 193 | let tuple_type = Self::parse_tuple(chars); 194 | generic_types.push(tuple_type); 195 | } 196 | ')' => { 197 | break; 198 | } 199 | ',' => { 200 | break; 201 | } 202 | ' ' => { 203 | // Ignore white spaces. 204 | chars.next(); 205 | } 206 | _ => { 207 | current_type.push(*c); 208 | chars.next(); 209 | } 210 | } 211 | } 212 | 213 | if !current_type.is_empty() { 214 | generic_types.push(AbiTypeAny::Basic((¤t_type).into())); 215 | } 216 | 217 | if generic_types.is_empty() { 218 | // TODO: check if this one may be handled as Basic("()"); 219 | Self::Basic("()".into()) 220 | } else if generic_types.len() == 1 { 221 | // Basic, Array or Generic with 1 inner type. 222 | generic_types.pop().unwrap() 223 | } else if chars.nth(0) == Some('(') { 224 | // Tuple. 225 | Self::Tuple(AbiTuple::new(generic_types)) 226 | } else { 227 | unreachable!(); 228 | } 229 | } 230 | 231 | /// Parses generic types detected between angle brackets. 232 | fn parse_generic(current_type: &str, chars: &mut Peekable) -> Self { 233 | let mut inners = vec![]; 234 | 235 | while let Some(c) = chars.peek() { 236 | match c { 237 | '>' => { 238 | chars.next(); 239 | break; 240 | } 241 | ',' => { 242 | chars.next(); 243 | } 244 | _ => { 245 | inners.push(Self::parse_type(chars)); 246 | } 247 | } 248 | } 249 | 250 | if inners.is_empty() { 251 | panic!("Array/Span/Generic type expects at least one inner type"); 252 | } 253 | 254 | // Array and Span are processed exactly the same, using `Vec`. 255 | let is_array = current_type.contains("core::array"); 256 | 257 | if is_array { 258 | if inners.len() == 1 { 259 | Self::Array(AbiArray::new(current_type, inners[0].clone())) 260 | } else { 261 | panic!("Array/Span expect exactly one inner type"); 262 | } 263 | } else { 264 | Self::Generic(AbiGeneric::new(current_type, inners)) 265 | } 266 | } 267 | 268 | /// Parses a tuple, which can also contains nested types. 269 | fn parse_tuple(chars: &mut Peekable) -> Self { 270 | let mut tuple_values = Vec::new(); 271 | 272 | if chars.next_if(|&x| x == ')').is_some() { 273 | return Self::Basic("()".into()); 274 | } 275 | 276 | while let Some(c) = chars.peek() { 277 | match c { 278 | ' ' => { 279 | chars.next(); 280 | } 281 | ',' => { 282 | chars.next(); 283 | } 284 | ')' => { 285 | chars.next(); 286 | break; 287 | } 288 | _ => { 289 | let v = Self::parse_type(chars); 290 | tuple_values.push(v); 291 | } 292 | } 293 | } 294 | 295 | Self::Tuple(AbiTuple::new(tuple_values)) 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /crates/parser/src/abi_types/tuple.rs: -------------------------------------------------------------------------------- 1 | use super::{AbiType, AbiTypeAny, GENTY_FROZEN}; 2 | 3 | #[derive(Debug, PartialEq, Clone)] 4 | pub struct AbiTuple { 5 | pub inners: Vec, 6 | pub genty: String, 7 | } 8 | 9 | impl AbiTuple { 10 | pub fn new(inners: Vec) -> Self { 11 | AbiTuple { 12 | inners, 13 | genty: String::new(), 14 | } 15 | } 16 | } 17 | 18 | impl From> for AbiTuple { 19 | fn from(v: Vec) -> Self { 20 | Self::new(v) 21 | } 22 | } 23 | 24 | impl AbiType for AbiTuple { 25 | fn get_genty(&self) -> String { 26 | self.genty.clone() 27 | } 28 | 29 | fn compare_generic(&mut self, other: &AbiTypeAny) { 30 | match other { 31 | AbiTypeAny::Tuple(_) => { 32 | if self.genty != GENTY_FROZEN { 33 | self.genty = other.get_genty(); 34 | } 35 | } 36 | _ => { 37 | for inner in &mut self.inners { 38 | inner.compare_generic(other); 39 | } 40 | } 41 | }; 42 | } 43 | 44 | fn apply_generic(&mut self, cairo_types_gentys: Vec<(&str, &str)>) -> (String, bool) { 45 | // Check if the whole tuple is the generic. 46 | for (cairo_type, genty) in &cairo_types_gentys { 47 | if &self.get_cairo_type_full() == cairo_type { 48 | self.genty = genty.to_string(); 49 | return (genty.to_string(), true); 50 | } 51 | } 52 | 53 | let mut tuple_has_generic = false; 54 | let mut s = "(".to_string(); 55 | let arr_len = self.inners.len(); 56 | 57 | for (idx, inner) in self.inners.iter_mut().enumerate() { 58 | let (type_str, is_generic) = inner.apply_generic(cairo_types_gentys.clone()); 59 | 60 | if is_generic && !tuple_has_generic { 61 | tuple_has_generic = true; 62 | } 63 | 64 | s.push_str(&type_str); 65 | 66 | if idx < arr_len - 1 { 67 | s.push_str(", "); 68 | } 69 | } 70 | s.push(')'); 71 | 72 | (s, tuple_has_generic) 73 | } 74 | 75 | fn get_cairo_type_full(&self) -> String { 76 | let mut s = "(".to_string(); 77 | for (idx, inner) in self.inners.iter().enumerate() { 78 | s.push_str(&inner.get_cairo_type_full()); 79 | 80 | if idx < self.inners.len() - 1 { 81 | s.push_str(", "); 82 | } 83 | } 84 | s.push(')'); 85 | s 86 | } 87 | 88 | fn get_cairo_type_name(&self) -> String { 89 | "|tuple|".to_string() 90 | } 91 | 92 | fn to_rust_type(&self) -> String { 93 | if !self.genty.is_empty() && self.genty != GENTY_FROZEN { 94 | self.genty.clone() 95 | } else { 96 | let mut s = "(".to_string(); 97 | for (idx, inner) in self.inners.iter().enumerate() { 98 | s.push_str(&inner.to_rust_type()); 99 | 100 | if idx < self.inners.len() - 1 { 101 | s.push_str(", "); 102 | } 103 | } 104 | s.push(')'); 105 | s 106 | } 107 | } 108 | 109 | fn to_rust_type_path(&self) -> String { 110 | if !self.genty.is_empty() && self.genty != GENTY_FROZEN { 111 | self.genty.clone() 112 | } else { 113 | let mut s = "(".to_string(); 114 | for (idx, inner) in self.inners.iter().enumerate() { 115 | s.push_str(&inner.to_rust_type_path()); 116 | 117 | if idx < self.inners.len() - 1 { 118 | s.push_str(", "); 119 | } 120 | } 121 | s.push(')'); 122 | s 123 | } 124 | } 125 | } 126 | 127 | #[cfg(test)] 128 | mod tests { 129 | use super::*; 130 | use crate::abi_types::{AbiArray, AbiTypeAny}; 131 | 132 | fn get_default() -> AbiTuple { 133 | AbiTuple::new(vec![ 134 | AbiTypeAny::Basic("core::felt252".into()), 135 | AbiTypeAny::Basic("core::integer::u32".into()), 136 | ]) 137 | } 138 | 139 | #[test] 140 | fn get_cairo_type_full() { 141 | let t = get_default(); 142 | assert_eq!( 143 | t.get_cairo_type_full(), 144 | "(core::felt252, core::integer::u32)" 145 | ); 146 | } 147 | 148 | #[test] 149 | fn cairo_type_name_only() { 150 | let t = get_default(); 151 | assert_eq!(t.get_cairo_type_name(), "|tuple|"); 152 | } 153 | 154 | #[test] 155 | fn to_rust_type() { 156 | let t = get_default(); 157 | assert_eq!( 158 | t.to_rust_type(), 159 | "(starknet::core::types::FieldElement, u32)" 160 | ); 161 | } 162 | 163 | #[test] 164 | fn to_rust_type_path() { 165 | let t = get_default(); 166 | assert_eq!( 167 | t.to_rust_type_path(), 168 | "(starknet::core::types::FieldElement, u32)" 169 | ); 170 | } 171 | 172 | #[test] 173 | fn from_string() { 174 | let t = AbiTypeAny::from_string("(core::felt252, core::integer::u32)"); 175 | assert_eq!(t, AbiTypeAny::Tuple(get_default())); 176 | } 177 | 178 | #[test] 179 | fn from_string_tuple_of_array() { 180 | let t = 181 | AbiTypeAny::from_string("(core::array::Array::, core::integer::u32)"); 182 | assert_eq!( 183 | t, 184 | AbiTypeAny::Tuple( 185 | vec![ 186 | AbiTypeAny::Array(AbiArray::new( 187 | "core::array::Array", 188 | AbiTypeAny::Basic("core::felt252".into()) 189 | )), 190 | AbiTypeAny::Basic("core::integer::u32".into()), 191 | ] 192 | .into() 193 | ) 194 | ); 195 | } 196 | 197 | #[test] 198 | fn generic_tuple() { 199 | let mut t = AbiTypeAny::from_string("(core::felt252, core::integer::u32)"); 200 | assert_eq!( 201 | t.apply_generic(vec![("(core::felt252, core::integer::u32)", "A")]), 202 | ("A".to_string(), true) 203 | ); 204 | } 205 | 206 | #[test] 207 | fn generic_inner() { 208 | let mut t = AbiTypeAny::from_string("(core::felt252, core::integer::u32)"); 209 | assert_eq!( 210 | t.apply_generic(vec![("core::felt252", "A")]), 211 | ("(A, core::integer::u32)".to_string(), true) 212 | ); 213 | } 214 | 215 | #[test] 216 | fn generic_inner_2() { 217 | let mut t = AbiTypeAny::from_string("(core::felt252, core::integer::u32)"); 218 | assert_eq!( 219 | t.apply_generic(vec![("core::integer::u32", "A")]), 220 | ("(core::felt252, A)".to_string(), true) 221 | ); 222 | } 223 | 224 | #[test] 225 | fn generic_tuple_not() { 226 | let mut t = AbiTypeAny::from_string("(core::felt252, core::integer::u32)"); 227 | assert_eq!( 228 | t.apply_generic(vec![("(core::u32, core::u256)", "A")]), 229 | ("(core::felt252, core::integer::u32)".to_string(), false) 230 | ); 231 | } 232 | 233 | #[test] 234 | fn generic_inner_not() { 235 | let mut t = AbiTypeAny::from_string("(core::felt252, core::integer::u32)"); 236 | assert_eq!( 237 | t.apply_generic(vec![("core::u256", "A")]), 238 | ("(core::felt252, core::integer::u32)".to_string(), false) 239 | ); 240 | } 241 | 242 | #[test] 243 | fn generic_inner_multiple() { 244 | let mut t = AbiTypeAny::from_string("(core::felt252, core::integer::u32)"); 245 | assert_eq!( 246 | t.apply_generic(vec![("core::felt252", "A"), ("core::integer::u32", "B")]), 247 | ("(A, B)".to_string(), true) 248 | ); 249 | } 250 | 251 | #[test] 252 | fn generic_inner_multiple_2() { 253 | let mut t = AbiTypeAny::from_string("(core::felt252, core::integer::u32)"); 254 | assert_eq!( 255 | t.apply_generic(vec![("core::array", "A"), ("core::integer::u32", "B")]), 256 | ("(core::felt252, B)".to_string(), true) 257 | ); 258 | } 259 | 260 | #[test] 261 | fn tuple_array_in_array() { 262 | let t = AbiTypeAny::from_string("(core::array::Span::, core::array::Span::>)"); 263 | 264 | assert_eq!( 265 | t.to_rust_type(), 266 | "(Vec, Vec>)" 267 | ); 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /crates/parser/src/cairo_enum.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use starknet::core::types::contract::AbiNamedMember; 4 | 5 | use super::abi_types::{AbiType, AbiTypeAny}; 6 | 7 | #[derive(Debug, Clone)] 8 | pub struct CairoEnum { 9 | pub abi: AbiTypeAny, 10 | /// Parsed types for each variants. 11 | pub variants: Vec<(String, AbiTypeAny)>, 12 | /// Variant name => (generic representation, is_generic). 13 | pub generic_variants: HashMap, 14 | } 15 | 16 | impl CairoEnum { 17 | /// Gets the name of the enum type. 18 | pub fn get_name(&self) -> String { 19 | self.abi.get_cairo_type_name() 20 | } 21 | 22 | /// Returns true if the enum is generic, false otherwise. 23 | pub fn is_generic(&self) -> bool { 24 | matches!(self.abi, AbiTypeAny::Generic(_)) 25 | } 26 | 27 | /// Returns the list of generic types, if any. 28 | pub fn get_gentys(&self) -> Vec { 29 | if let AbiTypeAny::Generic(g) = &self.abi { 30 | g.get_gentys_only() 31 | } else { 32 | vec![] 33 | } 34 | } 35 | 36 | /// Initializes a new instance from the abi name and it's variants. 37 | pub fn new(abi_name: &str, abi_variants: &Vec) -> CairoEnum { 38 | let abi = AbiTypeAny::from_string(abi_name); 39 | let mut variants: Vec<(String, AbiTypeAny)> = vec![]; 40 | let mut generic_variants: HashMap = HashMap::new(); 41 | 42 | for v in abi_variants { 43 | let name = v.name.clone(); 44 | let mut v_abi = AbiTypeAny::from_string(&v.r#type.clone()); 45 | 46 | if let AbiTypeAny::Generic(ref g) = abi { 47 | let cairo_gentys = g.get_cairo_types_gentys(); 48 | let cairo_gentys = cairo_gentys 49 | .iter() 50 | .map(|(v1, v2)| (&v1[..], &v2[..])) 51 | .collect(); 52 | 53 | let (type_str, is_generic) = v_abi.apply_generic(cairo_gentys); 54 | 55 | generic_variants.insert(name.clone(), (type_str.clone(), is_generic)); 56 | } 57 | 58 | variants.push((name.clone(), v_abi.clone())); 59 | } 60 | 61 | CairoEnum { 62 | abi, 63 | variants, 64 | generic_variants, 65 | } 66 | } 67 | 68 | /// Compares the generic types for each variants with an other `CairoEnum`. 69 | pub fn compare_generic_types(&self, existing_ce: &mut CairoEnum) { 70 | if let AbiTypeAny::Generic(_) = &self.abi { 71 | for (ev_name, ev_abi) in &mut existing_ce.variants { 72 | for (v_name, v_abi) in &self.variants { 73 | if v_name != ev_name { 74 | continue; 75 | } 76 | ev_abi.compare_generic(v_abi); 77 | } 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /crates/parser/src/cairo_event.rs: -------------------------------------------------------------------------------- 1 | //! Event parsing. 2 | use starknet::core::types::contract::{AbiEvent, AbiNamedMember, EventFieldKind, TypedAbiEvent}; 3 | 4 | use super::abi_types::{AbiType, AbiTypeAny}; 5 | use super::{CairoEnum, CairoStruct}; 6 | 7 | #[derive(Debug, Clone)] 8 | pub enum CairoEventInner { 9 | Enum(CairoEnum), 10 | Struct(CairoStruct), 11 | } 12 | 13 | #[derive(Debug, Clone)] 14 | pub struct CairoEvent { 15 | pub abi: AbiTypeAny, 16 | pub inner: CairoEventInner, 17 | pub fields_kinds: Vec, 18 | } 19 | 20 | impl CairoEvent { 21 | /// Gets the name of the struct type. 22 | pub fn get_name(&self) -> String { 23 | self.abi.get_cairo_type_name() 24 | } 25 | 26 | /// Gets the count for each field kind (keys, data). 27 | pub fn count_fields_kinds(&self) -> (usize, usize) { 28 | let mut k = 0; 29 | let mut d = 0; 30 | 31 | for fk in &self.fields_kinds { 32 | match fk { 33 | EventFieldKind::Key => k += 1, 34 | EventFieldKind::Data => d += 1, 35 | _ => continue, 36 | } 37 | } 38 | 39 | (k, d) 40 | } 41 | 42 | /// Initializes a new instance from the abi name and it's members. 43 | pub fn new(abi_event: &AbiEvent) -> Option { 44 | match abi_event { 45 | AbiEvent::Typed(typed_e) => match typed_e { 46 | TypedAbiEvent::Struct(s) => { 47 | if s.members.is_empty() { 48 | return None; 49 | } 50 | 51 | let name = &s.name; 52 | let mut kinds = vec![]; 53 | let members = s 54 | .members 55 | .iter() 56 | .map(|m| { 57 | kinds.push(m.kind.clone()); 58 | AbiNamedMember { 59 | name: m.name.clone(), 60 | r#type: m.r#type.clone(), 61 | } 62 | }) 63 | .collect(); 64 | 65 | let cs = CairoStruct::new(name, &members); 66 | 67 | Some(CairoEvent { 68 | abi: AbiTypeAny::from_string(name), 69 | inner: CairoEventInner::Struct(cs), 70 | fields_kinds: kinds, 71 | }) 72 | } 73 | TypedAbiEvent::Enum(e) => { 74 | if e.variants.is_empty() { 75 | return None; 76 | } 77 | 78 | let name = &e.name; 79 | let mut kinds = vec![]; 80 | let variants = e 81 | .variants 82 | .iter() 83 | .map(|v| { 84 | kinds.push(v.kind.clone()); 85 | AbiNamedMember { 86 | name: v.name.clone(), 87 | r#type: v.r#type.clone(), 88 | } 89 | }) 90 | .collect(); 91 | 92 | let ce = CairoEnum::new(name, &variants); 93 | 94 | Some(CairoEvent { 95 | abi: AbiTypeAny::from_string(name), 96 | inner: CairoEventInner::Enum(ce), 97 | fields_kinds: kinds, 98 | }) 99 | } 100 | }, 101 | AbiEvent::Untyped(_) => { 102 | // Can we support this..? 103 | //panic!("Untyped events are not supported"); 104 | None 105 | } 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /crates/parser/src/cairo_function.rs: -------------------------------------------------------------------------------- 1 | use starknet::core::types::contract::{AbiNamedMember, AbiOutput, StateMutability}; 2 | 3 | use super::abi_types::AbiTypeAny; 4 | 5 | #[derive(Debug, Clone)] 6 | pub struct CairoFunction { 7 | pub name: String, 8 | pub state_mutability: StateMutability, 9 | pub inputs: Vec<(String, AbiTypeAny)>, 10 | // For now, only one output type is supported (or none). 11 | // TODO: investigate the cases where more than one output is 12 | // present in the ABI. 13 | pub output: Option, 14 | } 15 | 16 | impl CairoFunction { 17 | /// Initializes a new instance from the abi name and it's members. 18 | pub fn new( 19 | abi_name: &str, 20 | state_mutability: StateMutability, 21 | inputs: &[AbiNamedMember], 22 | outputs: &Vec, 23 | ) -> CairoFunction { 24 | let name = abi_name.to_string(); 25 | 26 | let output = if !outputs.is_empty() { 27 | // For now, only first output is considered. 28 | // TODO: investigate when we can have several outputs. 29 | Some(AbiTypeAny::from_string(&outputs[0].r#type)) 30 | } else { 31 | None 32 | }; 33 | 34 | let inputs = inputs 35 | .iter() 36 | .map(|i| (i.name.clone(), AbiTypeAny::from_string(&i.r#type))) 37 | .collect(); 38 | 39 | CairoFunction { 40 | name, 41 | state_mutability, 42 | inputs, 43 | output, 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /crates/parser/src/cairo_struct.rs: -------------------------------------------------------------------------------- 1 | use starknet::core::types::contract::AbiNamedMember; 2 | use std::collections::HashMap; 3 | 4 | use super::abi_types::{AbiType, AbiTypeAny}; 5 | 6 | #[derive(Debug, Clone)] 7 | pub struct CairoStruct { 8 | pub abi: AbiTypeAny, 9 | /// Parsed types for each member. 10 | pub members: Vec<(String, AbiTypeAny)>, 11 | /// Members name => (generic representation, is_generic). 12 | pub generic_members: HashMap, 13 | } 14 | 15 | impl CairoStruct { 16 | /// Gets the name of the struct type. 17 | pub fn get_name(&self) -> String { 18 | self.abi.get_cairo_type_name() 19 | } 20 | 21 | /// Returns true if the struct is generic, false otherwise. 22 | pub fn is_generic(&self) -> bool { 23 | matches!(self.abi, AbiTypeAny::Generic(_)) 24 | } 25 | 26 | /// Returns the list of generic types, if any. 27 | pub fn get_gentys(&self) -> Vec { 28 | if let AbiTypeAny::Generic(g) = &self.abi { 29 | g.get_gentys_only() 30 | } else { 31 | vec![] 32 | } 33 | } 34 | 35 | /// Initializes a new instance from the abi name and it's members. 36 | pub fn new(abi_name: &str, abi_members: &Vec) -> CairoStruct { 37 | let abi = AbiTypeAny::from_string(abi_name); 38 | let mut members: Vec<(String, AbiTypeAny)> = vec![]; 39 | let mut generic_members: HashMap = HashMap::new(); 40 | 41 | for m in abi_members { 42 | let name = m.name.clone(); 43 | let mut m_abi = AbiTypeAny::from_string(&m.r#type.clone()); 44 | 45 | if let AbiTypeAny::Generic(ref g) = abi { 46 | let cairo_gentys = g.get_cairo_types_gentys(); 47 | let cairo_gentys = cairo_gentys 48 | .iter() 49 | .map(|(v1, v2)| (&v1[..], &v2[..])) 50 | .collect(); 51 | 52 | let (type_str, is_generic) = m_abi.apply_generic(cairo_gentys); 53 | 54 | generic_members.insert(name.clone(), (type_str.clone(), is_generic)); 55 | } 56 | 57 | members.push((name.clone(), m_abi.clone())); 58 | } 59 | 60 | CairoStruct { 61 | abi, 62 | members, 63 | generic_members, 64 | } 65 | } 66 | 67 | /// Compares the generic types for each members with an other `CairoStruct`. 68 | pub fn compare_generic_types(&self, existing_cs: &mut CairoStruct) { 69 | if let AbiTypeAny::Generic(_) = &self.abi { 70 | for (em_name, em_abi) in &mut existing_cs.members { 71 | for (m_name, m_abi) in &self.members { 72 | if m_name != em_name { 73 | continue; 74 | } 75 | em_abi.compare_generic(m_abi); 76 | } 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /crates/parser/src/cairo_types/error.rs: -------------------------------------------------------------------------------- 1 | use super::CairoType; 2 | 3 | use starknet::core::types::FieldElement; 4 | 5 | /// Cairo types result. 6 | pub type Result = core::result::Result; 7 | 8 | /// A cairo type error. 9 | #[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] 10 | pub enum Error { 11 | #[error("Invalid type found {0:?}.")] 12 | InvalidTypeString(String), 13 | #[error("Error during serialization {0:?}.")] 14 | Serialize(String), 15 | #[error("Error during deserialization {0:?}.")] 16 | Deserialize(String), 17 | } 18 | 19 | impl CairoType for Error { 20 | type RustType = Self; 21 | 22 | fn serialize(_rust: &Self::RustType) -> Vec { 23 | vec![] 24 | } 25 | 26 | fn deserialize(_felts: &[FieldElement], _offset: usize) -> Result { 27 | Ok(Error::Deserialize( 28 | "Error cairotype deserialized?".to_string(), 29 | )) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /crates/parser/src/cairo_types/mod.rs: -------------------------------------------------------------------------------- 1 | //! This crate contains the definition of traits and types 2 | //! that map to Cairo types. 3 | //! 4 | //! Some of the Cairo types are provided in the ABI event if they are very generic 5 | //! like `Option`, `Result`, etc... 6 | //! This crate provides the `CairoType` implementation for those types and all basic 7 | //! types from Cairo (integers, felt etc...). 8 | //! 9 | mod error; 10 | pub use error::{Error, Result}; 11 | 12 | pub mod types; 13 | pub use types::starknet::*; 14 | pub use types::*; 15 | 16 | use ::starknet::core::types::FieldElement; 17 | 18 | pub const CAIRO_TYPES_PATH: &str = "starknet_abigen_parser::cairo_types"; 19 | 20 | /// Basic cairo structs that are already implemented inside 21 | /// this crate and hence skipped during ABI generation. 22 | pub const CAIRO_BASIC_STRUCTS: [&str; 4] = ["Span", "ClassHash", "ContractAddress", "EthAddress"]; 23 | 24 | /// Same as `CAIRO_BASIC_STRUCTS`, but for enums. 25 | pub const CAIRO_BASIC_ENUMS: [&str; 3] = ["Option", "Result", "bool"]; 26 | 27 | /// CairoType trait to implement in order to serialize/deserialize 28 | /// a Rust type to/from a CairoType. 29 | pub trait CairoType { 30 | /// The corresponding Rust type. 31 | type RustType; 32 | 33 | /// The serialized size of the type in felts, if known at compile time. 34 | const SERIALIZED_SIZE: Option = Some(1); 35 | 36 | /// Whether the serialized size is dynamic. 37 | const DYNAMIC: bool = Self::SERIALIZED_SIZE.is_none(); 38 | 39 | /// Calculates the serialized size of the data for a single felt 40 | /// it will always be 1. 41 | /// If the type is dynamic, SERIALIZED_SIZE is None, but this 42 | /// function is overriden to correctly compute the size. 43 | #[inline] 44 | fn serialized_size(_rust: &Self::RustType) -> usize { 45 | Self::SERIALIZED_SIZE.unwrap() 46 | } 47 | 48 | /// Serializes the given type into a FieldElement sequence. 49 | fn serialize(rust: &Self::RustType) -> Vec; 50 | 51 | /// TODO: add serialize_to(rust: &Self::RustType, out: &mut Vec) 52 | /// for large buffers optimization. 53 | 54 | /// Deserializes an array of felts into the given type. 55 | fn deserialize(felts: &[FieldElement], offset: usize) -> Result; 56 | } 57 | -------------------------------------------------------------------------------- /crates/parser/src/cairo_types/types/array.rs: -------------------------------------------------------------------------------- 1 | //! CairoType implementation for `Vec`. 2 | //! They are used for Array and Span cairo types. 3 | use crate::cairo_types::{CairoType, Error, Result}; 4 | use starknet::core::types::FieldElement; 5 | 6 | impl CairoType for Vec 7 | where 8 | T: CairoType, 9 | { 10 | type RustType = Vec; 11 | 12 | const SERIALIZED_SIZE: Option = None; 13 | 14 | #[inline] 15 | fn serialized_size(rust: &Self::RustType) -> usize { 16 | let data = rust; 17 | // 1 + because the length is always the first felt. 18 | 1 + data.iter().map(T::serialized_size).sum::() 19 | } 20 | 21 | fn serialize(rust: &Self::RustType) -> Vec { 22 | let mut out: Vec = vec![rust.len().into()]; 23 | rust.iter().for_each(|r| out.extend(T::serialize(r))); 24 | out 25 | } 26 | 27 | fn deserialize(felts: &[FieldElement], offset: usize) -> Result { 28 | let len: usize = usize::from_str_radix(format!("{:x}", felts[offset]).as_str(), 16) 29 | .map_err(|_| { 30 | Error::Deserialize("First felt of an array must fit into usize".to_string()) 31 | })?; 32 | 33 | let mut out: Vec = vec![]; 34 | let mut offset = offset + 1; 35 | 36 | loop { 37 | if out.len() == len { 38 | break; 39 | } 40 | 41 | let rust: RT = T::deserialize(felts, offset)?; 42 | offset += T::serialized_size(&rust); 43 | out.push(rust); 44 | } 45 | 46 | Ok(out) 47 | } 48 | } 49 | 50 | #[cfg(test)] 51 | mod tests { 52 | use super::*; 53 | 54 | #[test] 55 | fn test_serialize_array() { 56 | let v: Vec = vec![1, 2, 3]; 57 | let felts = Vec::::serialize(&v); 58 | assert_eq!(felts.len(), 4); 59 | assert_eq!(felts[0], FieldElement::from(3_u32)); 60 | assert_eq!(felts[1], FieldElement::ONE); 61 | assert_eq!(felts[2], FieldElement::TWO); 62 | assert_eq!(felts[3], FieldElement::THREE); 63 | } 64 | 65 | #[test] 66 | fn test_deserialize_array() { 67 | let felts: Vec = vec![ 68 | FieldElement::from(2_u32), 69 | FieldElement::from(123_u32), 70 | FieldElement::from(9988_u32), 71 | ]; 72 | 73 | let vals = Vec::::deserialize(&felts, 0).unwrap(); 74 | assert_eq!(vals.len(), 2); 75 | assert_eq!(vals[0], 123_u32); 76 | assert_eq!(vals[1], 9988_u32); 77 | } 78 | 79 | #[test] 80 | fn test_serialize_array_nested() { 81 | let v: Vec> = vec![vec![1, 2], vec![3]]; 82 | let felts = Vec::>::serialize(&v); 83 | assert_eq!(felts.len(), 6); 84 | assert_eq!(felts[0], FieldElement::TWO); 85 | assert_eq!(felts[1], FieldElement::TWO); 86 | assert_eq!(felts[2], FieldElement::ONE); 87 | assert_eq!(felts[3], FieldElement::TWO); 88 | assert_eq!(felts[4], FieldElement::ONE); 89 | assert_eq!(felts[5], FieldElement::THREE); 90 | } 91 | 92 | #[test] 93 | fn test_deserialize_array_nested() { 94 | let felts: Vec = vec![ 95 | FieldElement::TWO, 96 | FieldElement::TWO, 97 | FieldElement::ONE, 98 | FieldElement::TWO, 99 | FieldElement::ONE, 100 | FieldElement::THREE, 101 | ]; 102 | 103 | let vals = Vec::>::deserialize(&felts, 0).unwrap(); 104 | assert_eq!(vals.len(), 2); 105 | assert_eq!(vals[0], vec![1, 2]); 106 | assert_eq!(vals[1], vec![3]); 107 | } 108 | 109 | #[test] 110 | fn test_serialize_array_tuple() { 111 | let v: Vec<(u32, FieldElement)> = vec![(12, FieldElement::TWO)]; 112 | let felts = Vec::<(u32, FieldElement)>::serialize(&v); 113 | assert_eq!(felts.len(), 3); 114 | assert_eq!(felts[0], FieldElement::from(1_u32)); 115 | assert_eq!(felts[1], FieldElement::from(12_u32)); 116 | assert_eq!(felts[2], FieldElement::TWO); 117 | } 118 | 119 | #[test] 120 | fn test_deserialize_array_tuple() { 121 | let felts: Vec = vec![ 122 | FieldElement::from(1_u32), 123 | FieldElement::from(12_u32), 124 | FieldElement::TWO, 125 | ]; 126 | 127 | let vals = Vec::<(u32, FieldElement)>::deserialize(&felts, 0).unwrap(); 128 | assert_eq!(vals.len(), 1); 129 | assert_eq!(vals[0], (12, FieldElement::TWO)); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /crates/parser/src/cairo_types/types/boolean.rs: -------------------------------------------------------------------------------- 1 | //! CairoType implementation for bool. 2 | use crate::cairo_types::{CairoType, Result}; 3 | use starknet::core::types::FieldElement; 4 | 5 | impl CairoType for bool { 6 | type RustType = Self; 7 | 8 | fn serialize(rust: &Self::RustType) -> Vec { 9 | vec![FieldElement::from(*rust as u32)] 10 | } 11 | 12 | fn deserialize(felts: &[FieldElement], offset: usize) -> Result { 13 | if felts[offset] == FieldElement::ONE { 14 | Ok(true) 15 | } else { 16 | Ok(false) 17 | } 18 | } 19 | } 20 | 21 | #[cfg(test)] 22 | mod tests { 23 | use super::*; 24 | 25 | #[test] 26 | fn test_serialize_bool() { 27 | let v = true; 28 | let felts = bool::serialize(&v); 29 | assert_eq!(felts.len(), 1); 30 | assert_eq!(felts[0], FieldElement::ONE); 31 | 32 | let v = false; 33 | let felts = bool::serialize(&v); 34 | assert_eq!(felts.len(), 1); 35 | assert_eq!(felts[0], FieldElement::ZERO); 36 | } 37 | 38 | #[test] 39 | fn test_deserialize_bool() { 40 | let felts = vec![FieldElement::ZERO, FieldElement::ONE, FieldElement::TWO]; 41 | assert!(!bool::deserialize(&felts, 0).unwrap()); 42 | assert!(bool::deserialize(&felts, 1).unwrap()); 43 | assert!(!bool::deserialize(&felts, 2).unwrap()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /crates/parser/src/cairo_types/types/felt.rs: -------------------------------------------------------------------------------- 1 | use crate::cairo_types::{CairoType, Result}; 2 | use starknet::core::types::FieldElement; 3 | 4 | impl CairoType for () { 5 | type RustType = Self; 6 | 7 | fn serialize(_rust: &Self::RustType) -> Vec { 8 | vec![] 9 | } 10 | 11 | fn deserialize(_felts: &[FieldElement], _offset: usize) -> Result { 12 | Ok(()) 13 | } 14 | } 15 | 16 | impl CairoType for FieldElement { 17 | type RustType = Self; 18 | 19 | fn serialize(rust: &Self::RustType) -> Vec { 20 | vec![*rust] 21 | } 22 | 23 | fn deserialize(felts: &[FieldElement], offset: usize) -> Result { 24 | Ok(felts[offset]) 25 | } 26 | } 27 | 28 | #[cfg(test)] 29 | mod tests { 30 | use super::*; 31 | 32 | #[test] 33 | fn test_serialize_field_element() { 34 | let f = FieldElement::ZERO; 35 | let felts = FieldElement::serialize(&f); 36 | assert_eq!(felts.len(), 1); 37 | assert_eq!(felts[0], FieldElement::ZERO); 38 | } 39 | 40 | #[test] 41 | fn test_deserialize_field_element() { 42 | let felts = vec![FieldElement::ZERO, FieldElement::ONE, FieldElement::TWO]; 43 | assert_eq!( 44 | FieldElement::deserialize(&felts, 0).unwrap(), 45 | FieldElement::ZERO 46 | ); 47 | assert_eq!( 48 | FieldElement::deserialize(&felts, 1).unwrap(), 49 | FieldElement::ONE 50 | ); 51 | assert_eq!( 52 | FieldElement::deserialize(&felts, 2).unwrap(), 53 | FieldElement::TWO 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /crates/parser/src/cairo_types/types/integers.rs: -------------------------------------------------------------------------------- 1 | //! CairoType implementation for integers (signed/unsigned). 2 | use crate::cairo_types::{CairoType, Result}; 3 | use starknet::core::types::FieldElement; 4 | 5 | macro_rules! implement_trait_for_unsigned { 6 | ($type:ty) => { 7 | impl CairoType for $type { 8 | type RustType = Self; 9 | 10 | fn serialize(rust: &Self::RustType) -> Vec { 11 | vec![FieldElement::from(*rust)] 12 | } 13 | 14 | fn deserialize(felts: &[FieldElement], offset: usize) -> Result { 15 | let temp: u128 = felts[offset].try_into().unwrap(); 16 | Ok(temp as $type) 17 | } 18 | } 19 | }; 20 | } 21 | 22 | macro_rules! implement_trait_for_signed { 23 | ($type:ty) => { 24 | impl CairoType for $type { 25 | type RustType = Self; 26 | 27 | fn serialize(rust: &Self::RustType) -> Vec { 28 | vec![FieldElement::from(*rust as usize)] 29 | } 30 | 31 | fn deserialize(felts: &[FieldElement], offset: usize) -> Result { 32 | let temp: u128 = felts[offset].try_into().unwrap(); 33 | Ok(temp as $type) 34 | } 35 | } 36 | }; 37 | } 38 | 39 | implement_trait_for_unsigned!(u8); 40 | implement_trait_for_unsigned!(u16); 41 | implement_trait_for_unsigned!(u32); 42 | implement_trait_for_unsigned!(u64); 43 | implement_trait_for_unsigned!(u128); 44 | implement_trait_for_unsigned!(usize); 45 | 46 | implement_trait_for_signed!(i8); 47 | implement_trait_for_signed!(i16); 48 | implement_trait_for_signed!(i32); 49 | implement_trait_for_signed!(i64); 50 | implement_trait_for_signed!(i128); 51 | 52 | #[cfg(test)] 53 | mod tests { 54 | use super::*; 55 | 56 | #[test] 57 | fn test_serialize_u8() { 58 | let v = 12_u8; 59 | let felts = u8::serialize(&v); 60 | assert_eq!(felts.len(), 1); 61 | assert_eq!(felts[0], FieldElement::from(12_u8)); 62 | } 63 | 64 | #[test] 65 | fn test_deserialize_u8() { 66 | let felts = vec![FieldElement::from(12_u8), FieldElement::from(10_u8)]; 67 | assert_eq!(u8::deserialize(&felts, 0).unwrap(), 12); 68 | assert_eq!(u8::deserialize(&felts, 1).unwrap(), 10); 69 | } 70 | 71 | #[test] 72 | fn test_serialize_u16() { 73 | let v = 12_u16; 74 | let felts = u16::serialize(&v); 75 | assert_eq!(felts.len(), 1); 76 | assert_eq!(felts[0], FieldElement::from(12_u16)); 77 | } 78 | 79 | #[test] 80 | fn test_deserialize_u16() { 81 | let felts = vec![FieldElement::from(12_u16), FieldElement::from(10_u8)]; 82 | assert_eq!(u16::deserialize(&felts, 0).unwrap(), 12); 83 | assert_eq!(u16::deserialize(&felts, 1).unwrap(), 10); 84 | } 85 | 86 | #[test] 87 | fn test_serialize_u32() { 88 | let v = 123_u32; 89 | let felts = u32::serialize(&v); 90 | assert_eq!(felts.len(), 1); 91 | assert_eq!(felts[0], FieldElement::from(123_u32)); 92 | } 93 | 94 | #[test] 95 | fn test_deserialize_u32() { 96 | let felts = vec![FieldElement::from(123_u32), FieldElement::from(99_u32)]; 97 | assert_eq!(u32::deserialize(&felts, 0).unwrap(), 123); 98 | assert_eq!(u32::deserialize(&felts, 1).unwrap(), 99); 99 | } 100 | 101 | #[test] 102 | fn test_serialize_u64() { 103 | let v = 123_u64; 104 | let felts = u64::serialize(&v); 105 | assert_eq!(felts.len(), 1); 106 | assert_eq!(felts[0], FieldElement::from(123_u64)); 107 | } 108 | 109 | #[test] 110 | fn test_deserialize_u64() { 111 | let felts = vec![FieldElement::from(123_u64), FieldElement::from(99_u64)]; 112 | assert_eq!(u64::deserialize(&felts, 0).unwrap(), 123); 113 | assert_eq!(u64::deserialize(&felts, 1).unwrap(), 99); 114 | } 115 | 116 | #[test] 117 | fn test_serialize_u128() { 118 | let v = 123_u128; 119 | let felts = u128::serialize(&v); 120 | assert_eq!(felts.len(), 1); 121 | assert_eq!(felts[0], FieldElement::from(123_u128)); 122 | } 123 | 124 | #[test] 125 | fn test_deserialize_u128() { 126 | let felts = vec![FieldElement::from(123_u128), FieldElement::from(99_u128)]; 127 | assert_eq!(u128::deserialize(&felts, 0).unwrap(), 123); 128 | assert_eq!(u128::deserialize(&felts, 1).unwrap(), 99); 129 | } 130 | 131 | #[test] 132 | fn test_serialize_usize() { 133 | let v = 123; 134 | let felts = usize::serialize(&v); 135 | assert_eq!(felts.len(), 1); 136 | assert_eq!(felts[0], FieldElement::from(123_u128)); 137 | } 138 | 139 | #[test] 140 | fn test_deserialize_usize() { 141 | let felts = vec![FieldElement::from(123_u128), FieldElement::from(99_u64)]; 142 | assert_eq!(usize::deserialize(&felts, 0).unwrap(), 123); 143 | assert_eq!(usize::deserialize(&felts, 1).unwrap(), 99); 144 | } 145 | 146 | #[test] 147 | fn test_serialize_i8() { 148 | let v = i8::MAX; 149 | let felts = i8::serialize(&v); 150 | assert_eq!(felts.len(), 1); 151 | assert_eq!(felts[0], FieldElement::from(i8::MAX as u8)); 152 | } 153 | 154 | #[test] 155 | fn test_deserialize_i8() { 156 | let felts = vec![ 157 | FieldElement::from(i8::MAX as u8), 158 | FieldElement::from(i8::MAX as u8), 159 | ]; 160 | assert_eq!(i8::deserialize(&felts, 0).unwrap(), i8::MAX); 161 | assert_eq!(i8::deserialize(&felts, 1).unwrap(), i8::MAX); 162 | } 163 | 164 | #[test] 165 | fn test_serialize_i16() { 166 | let v = i16::MAX; 167 | let felts = i16::serialize(&v); 168 | assert_eq!(felts.len(), 1); 169 | assert_eq!(felts[0], FieldElement::from(i16::MAX as u16)); 170 | } 171 | 172 | #[test] 173 | fn test_deserialize_i16() { 174 | let felts = vec![ 175 | FieldElement::from(i16::MAX as u16), 176 | FieldElement::from(i16::MAX as u16), 177 | ]; 178 | assert_eq!(i16::deserialize(&felts, 0).unwrap(), i16::MAX); 179 | assert_eq!(i16::deserialize(&felts, 1).unwrap(), i16::MAX); 180 | } 181 | 182 | #[test] 183 | fn test_serialize_i32() { 184 | let v = i32::MAX; 185 | let felts = i32::serialize(&v); 186 | assert_eq!(felts.len(), 1); 187 | assert_eq!(felts[0], FieldElement::from(i32::MAX as u32)); 188 | } 189 | 190 | #[test] 191 | fn test_deserialize_i32() { 192 | let felts = vec![ 193 | FieldElement::from(i32::MAX as u32), 194 | FieldElement::from(i32::MAX as u32), 195 | ]; 196 | assert_eq!(i32::deserialize(&felts, 0).unwrap(), i32::MAX); 197 | assert_eq!(i32::deserialize(&felts, 1).unwrap(), i32::MAX); 198 | } 199 | 200 | #[test] 201 | fn test_serialize_i64() { 202 | let v = i64::MAX; 203 | let felts = i64::serialize(&v); 204 | assert_eq!(felts.len(), 1); 205 | assert_eq!(felts[0], FieldElement::from(i64::MAX as u64)); 206 | } 207 | 208 | #[test] 209 | fn test_deserialize_i64() { 210 | let felts = vec![ 211 | FieldElement::from(i64::MAX as u64), 212 | FieldElement::from(i64::MAX as u64), 213 | ]; 214 | assert_eq!(i64::deserialize(&felts, 0).unwrap(), i64::MAX); 215 | assert_eq!(i64::deserialize(&felts, 1).unwrap(), i64::MAX); 216 | } 217 | 218 | #[test] 219 | fn test_deserialize_i128() { 220 | let felts = vec![ 221 | FieldElement::from(i128::MAX as u128), 222 | FieldElement::from(i128::MAX as u128), 223 | ]; 224 | assert_eq!(i128::deserialize(&felts, 0).unwrap(), i128::MAX); 225 | assert_eq!(i128::deserialize(&felts, 1).unwrap(), i128::MAX); 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /crates/parser/src/cairo_types/types/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod array; 2 | pub mod boolean; 3 | pub mod felt; 4 | pub mod integers; 5 | pub mod option; 6 | pub mod result; 7 | pub mod starknet; 8 | pub mod tuple; 9 | -------------------------------------------------------------------------------- /crates/parser/src/cairo_types/types/option.rs: -------------------------------------------------------------------------------- 1 | //! CairoType implementation for Option. 2 | //! 3 | //! In cairo, `Some` is the first field and `None` the second one. 4 | //! To follow the serialization rule, `Some` has index 0, and `None` index 1. 5 | //! 6 | //! https://github.com/starkware-libs/cairo/blob/main/corelib/src/option.cairo#L6 7 | use crate::cairo_types::{CairoType, Error, Result}; 8 | use starknet::core::types::FieldElement; 9 | 10 | impl CairoType for Option 11 | where 12 | T: CairoType, 13 | { 14 | type RustType = Option; 15 | 16 | fn serialize(rust: &Self::RustType) -> Vec { 17 | let mut out = vec![]; 18 | 19 | match rust { 20 | Some(r) => { 21 | out.push(FieldElement::ZERO); 22 | out.extend(T::serialize(r)); 23 | } 24 | None => out.push(FieldElement::ONE), 25 | }; 26 | 27 | out 28 | } 29 | 30 | fn deserialize(felts: &[FieldElement], offset: usize) -> Result { 31 | let idx = felts[offset]; 32 | 33 | if idx == FieldElement::ZERO { 34 | // + 1 as the offset value is the index of the enum. 35 | Ok(Option::Some(T::deserialize(felts, offset + 1)?)) 36 | } else if idx == FieldElement::ONE { 37 | Ok(Option::None) 38 | } else { 39 | Err(Error::Deserialize( 40 | "Option is expected 0 or 1 index only".to_string(), 41 | )) 42 | } 43 | } 44 | } 45 | 46 | #[cfg(test)] 47 | mod tests { 48 | use super::*; 49 | use starknet::core::types::FieldElement; 50 | 51 | #[test] 52 | fn test_option_some_serialize() { 53 | let o = Some(u32::MAX); 54 | let felts = Option::::serialize(&o); 55 | assert_eq!(felts.len(), 2); 56 | assert_eq!(felts[0], FieldElement::ZERO); 57 | assert_eq!(felts[1], FieldElement::from(u32::MAX)); 58 | } 59 | 60 | #[test] 61 | fn test_option_some_deserialize() { 62 | let felts = vec![FieldElement::ZERO, FieldElement::from(u32::MAX)]; 63 | let o = Option::::deserialize(&felts, 0).unwrap(); 64 | assert_eq!(o, Some(u32::MAX)); 65 | 66 | let felts = vec![ 67 | FieldElement::THREE, 68 | FieldElement::ZERO, 69 | FieldElement::from(u32::MAX), 70 | ]; 71 | let o = Option::::deserialize(&felts, 1).unwrap(); 72 | assert_eq!(o, Some(u32::MAX)); 73 | } 74 | 75 | #[test] 76 | fn test_option_some_array_serialize() { 77 | let o = Some(vec![u32::MAX, u32::MAX]); 78 | let felts = Option::>::serialize(&o); 79 | assert_eq!(felts.len(), 4); 80 | assert_eq!(felts[0], FieldElement::ZERO); 81 | assert_eq!(felts[1], FieldElement::from(2_u32)); 82 | assert_eq!(felts[2], FieldElement::from(u32::MAX)); 83 | assert_eq!(felts[3], FieldElement::from(u32::MAX)); 84 | } 85 | 86 | #[test] 87 | fn test_option_some_array_deserialize() { 88 | let felts = vec![ 89 | FieldElement::ZERO, 90 | FieldElement::from(2_u32), 91 | FieldElement::from(u32::MAX), 92 | FieldElement::from(u32::MAX), 93 | ]; 94 | let o = Option::>::deserialize(&felts, 0).unwrap(); 95 | assert_eq!(o, Some(vec![u32::MAX, u32::MAX])); 96 | 97 | let felts = vec![ 98 | FieldElement::THREE, 99 | FieldElement::ZERO, 100 | FieldElement::from(2_u32), 101 | FieldElement::from(u32::MAX), 102 | FieldElement::from(u32::MAX), 103 | ]; 104 | let o = Option::>::deserialize(&felts, 1).unwrap(); 105 | assert_eq!(o, Some(vec![u32::MAX, u32::MAX])); 106 | } 107 | 108 | #[test] 109 | fn test_option_none_serialize() { 110 | let o: Option = None; 111 | let felts = Option::::serialize(&o); 112 | assert_eq!(felts.len(), 1); 113 | assert_eq!(felts[0], FieldElement::ONE); 114 | } 115 | 116 | #[test] 117 | fn test_option_none_deserialize() { 118 | let felts = vec![FieldElement::ONE]; 119 | let o = Option::::deserialize(&felts, 0).unwrap(); 120 | assert_eq!(o, None); 121 | 122 | let felts = vec![FieldElement::THREE, FieldElement::ONE]; 123 | let o = Option::::deserialize(&felts, 1).unwrap(); 124 | assert_eq!(o, None); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /crates/parser/src/cairo_types/types/result.rs: -------------------------------------------------------------------------------- 1 | //! CairoType implementation for Result. 2 | //! 3 | //! https://github.com/starkware-libs/cairo/blob/main/corelib/src/result.cairo#L6 4 | use crate::cairo_types::{CairoType, Error as CairoError, Result as CairoResult}; 5 | use starknet::core::types::FieldElement; 6 | 7 | impl CairoType for Result 8 | where 9 | T: CairoType, 10 | E: CairoType, 11 | { 12 | type RustType = Result; 13 | 14 | fn serialize(rust: &Self::RustType) -> Vec { 15 | let mut out = vec![]; 16 | 17 | match rust { 18 | Result::Ok(r) => { 19 | out.push(FieldElement::ZERO); 20 | out.extend(T::serialize(r)); 21 | } 22 | Result::Err(e) => { 23 | out.push(FieldElement::ONE); 24 | out.extend(E::serialize(e)); 25 | } 26 | }; 27 | 28 | out 29 | } 30 | 31 | fn deserialize(felts: &[FieldElement], offset: usize) -> CairoResult { 32 | let idx = felts[offset]; 33 | 34 | if idx == FieldElement::ZERO { 35 | // + 1 as the offset value is the index of the enum. 36 | CairoResult::Ok(Ok(T::deserialize(felts, offset + 1)?)) 37 | } else if idx == FieldElement::ONE { 38 | CairoResult::Ok(Err(E::deserialize(felts, offset + 1)?)) 39 | } else { 40 | Err(CairoError::Deserialize( 41 | "Result is expected 0 or 1 index only".to_string(), 42 | )) 43 | } 44 | } 45 | } 46 | 47 | #[cfg(test)] 48 | mod tests { 49 | use super::*; 50 | use starknet::core::types::FieldElement; 51 | 52 | #[test] 53 | fn test_result_ok_serialize() { 54 | let r = Ok(u32::MAX); 55 | let felts = Result::::serialize(&r); 56 | assert_eq!(felts.len(), 2); 57 | assert_eq!(felts[0], FieldElement::ZERO); 58 | assert_eq!(felts[1], FieldElement::from(u32::MAX)); 59 | } 60 | 61 | #[test] 62 | fn test_result_ok_deserialize() { 63 | let felts = vec![FieldElement::ZERO, FieldElement::from(u32::MAX)]; 64 | let r = Result::::deserialize(&felts, 0).unwrap(); 65 | assert_eq!(r, Ok(u32::MAX)); 66 | } 67 | 68 | #[test] 69 | fn test_result_ok_unit_serialize() { 70 | let r = Ok(()); 71 | let felts = Result::<(), FieldElement>::serialize(&r); 72 | assert_eq!(felts.len(), 1); 73 | assert_eq!(felts[0], FieldElement::ZERO); 74 | } 75 | 76 | #[test] 77 | fn test_result_ok_unit_deserialize() { 78 | let felts = vec![FieldElement::ZERO]; 79 | let r = Result::<(), FieldElement>::deserialize(&felts, 0).unwrap(); 80 | assert_eq!(r, Ok(())); 81 | } 82 | 83 | #[test] 84 | fn test_result_err_serialize() { 85 | let r = Err(FieldElement::ONE); 86 | let felts = Result::::serialize(&r); 87 | assert_eq!(felts.len(), 2); 88 | assert_eq!(felts[0], FieldElement::ONE); 89 | assert_eq!(felts[1], FieldElement::ONE); 90 | } 91 | 92 | #[test] 93 | fn test_result_err_deserialize() { 94 | let felts = vec![FieldElement::ONE, FieldElement::ONE]; 95 | let r = Result::::deserialize(&felts, 0).unwrap(); 96 | assert_eq!(r, Err(FieldElement::ONE)); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /crates/parser/src/cairo_types/types/starknet.rs: -------------------------------------------------------------------------------- 1 | //! CairoType implementation for starknet types. 2 | //! 3 | //! They are alf `FieldElement` under the hood. 4 | use crate::cairo_types::{CairoType, Result}; 5 | use starknet::core::types::FieldElement; 6 | 7 | /// ContractAddress. 8 | #[derive(Debug, Copy, Clone, PartialEq)] 9 | pub struct ContractAddress(pub FieldElement); 10 | 11 | impl From for ContractAddress { 12 | fn from(item: FieldElement) -> Self { 13 | Self(item) 14 | } 15 | } 16 | 17 | impl From for FieldElement { 18 | fn from(item: ContractAddress) -> Self { 19 | item.0 20 | } 21 | } 22 | 23 | impl CairoType for ContractAddress { 24 | type RustType = Self; 25 | 26 | fn serialize(rust: &Self::RustType) -> Vec { 27 | FieldElement::serialize(&rust.0) 28 | } 29 | 30 | fn deserialize(felts: &[FieldElement], offset: usize) -> Result { 31 | Ok(ContractAddress(FieldElement::deserialize(felts, offset)?)) 32 | } 33 | } 34 | 35 | /// ClassHash. 36 | #[derive(Debug, Copy, Clone, PartialEq)] 37 | pub struct ClassHash(pub FieldElement); 38 | 39 | impl From for ClassHash { 40 | fn from(item: FieldElement) -> Self { 41 | Self(item) 42 | } 43 | } 44 | 45 | impl From for FieldElement { 46 | fn from(item: ClassHash) -> Self { 47 | item.0 48 | } 49 | } 50 | 51 | impl CairoType for ClassHash { 52 | type RustType = Self; 53 | 54 | fn serialize(rust: &Self::RustType) -> Vec { 55 | FieldElement::serialize(&rust.0) 56 | } 57 | 58 | fn deserialize(felts: &[FieldElement], offset: usize) -> Result { 59 | Ok(ClassHash(FieldElement::deserialize(felts, offset)?)) 60 | } 61 | } 62 | 63 | /// EthAddress. 64 | #[derive(Debug, Copy, Clone, PartialEq)] 65 | pub struct EthAddress(pub FieldElement); 66 | 67 | impl From for EthAddress { 68 | fn from(item: FieldElement) -> Self { 69 | Self(item) 70 | } 71 | } 72 | 73 | impl From for FieldElement { 74 | fn from(item: EthAddress) -> Self { 75 | item.0 76 | } 77 | } 78 | 79 | impl CairoType for EthAddress { 80 | type RustType = Self; 81 | 82 | fn serialize(rust: &Self::RustType) -> Vec { 83 | FieldElement::serialize(&rust.0) 84 | } 85 | 86 | fn deserialize(felts: &[FieldElement], offset: usize) -> Result { 87 | Ok(EthAddress(FieldElement::deserialize(felts, offset)?)) 88 | } 89 | } 90 | 91 | #[cfg(test)] 92 | mod tests { 93 | use super::*; 94 | 95 | #[test] 96 | fn test_contract_address_serialize() { 97 | let contract_address = ContractAddress(FieldElement::from(1_u32)); 98 | let felts = ContractAddress::serialize(&contract_address); 99 | assert_eq!(felts.len(), 1); 100 | assert_eq!(felts[0], FieldElement::from(1_u32)); 101 | } 102 | 103 | #[test] 104 | fn test_contract_address_deserialize() { 105 | let felts = vec![FieldElement::from(1_u32)]; 106 | let contract_address = ContractAddress::deserialize(&felts, 0).unwrap(); 107 | assert_eq!(contract_address, ContractAddress(FieldElement::from(1_u32))) 108 | } 109 | 110 | #[test] 111 | fn test_class_hash_serialize() { 112 | let class_hash = ClassHash(FieldElement::from(1_u32)); 113 | let felts = ClassHash::serialize(&class_hash); 114 | assert_eq!(felts.len(), 1); 115 | assert_eq!(felts[0], FieldElement::from(1_u32)); 116 | } 117 | 118 | #[test] 119 | fn test_class_hash_deserialize() { 120 | let felts = vec![FieldElement::from(1_u32)]; 121 | let class_hash = ClassHash::deserialize(&felts, 0).unwrap(); 122 | assert_eq!(class_hash, ClassHash(FieldElement::from(1_u32))) 123 | } 124 | 125 | #[test] 126 | fn test_eth_address_serialize() { 127 | let eth_address = EthAddress(FieldElement::from(1_u32)); 128 | let felts = EthAddress::serialize(ð_address); 129 | assert_eq!(felts.len(), 1); 130 | assert_eq!(felts[0], FieldElement::from(1_u32)); 131 | } 132 | 133 | #[test] 134 | fn test_eth_address_deserialize() { 135 | let felts = vec![FieldElement::from(1_u32)]; 136 | let eth_address = EthAddress::deserialize(&felts, 0).unwrap(); 137 | assert_eq!(eth_address, EthAddress(FieldElement::from(1_u32))) 138 | } 139 | 140 | #[test] 141 | fn test_contract_address_from() { 142 | let contract_address = ContractAddress::from(FieldElement::from(1_u32)); 143 | assert_eq!(contract_address, ContractAddress(FieldElement::from(1_u32))) 144 | } 145 | 146 | #[test] 147 | fn test_class_hash_from() { 148 | let class_hash = ClassHash::from(FieldElement::from(1_u32)); 149 | assert_eq!(class_hash, ClassHash(FieldElement::from(1_u32))) 150 | } 151 | 152 | #[test] 153 | fn test_eth_address_from() { 154 | let eth_address = EthAddress::from(FieldElement::from(1_u32)); 155 | assert_eq!(eth_address, EthAddress(FieldElement::from(1_u32))) 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /crates/parser/src/cairo_types/types/tuple.rs: -------------------------------------------------------------------------------- 1 | //! CairoType implementation for tuples. 2 | use crate::cairo_types::{CairoType, Result}; 3 | use starknet::core::types::FieldElement; 4 | 5 | macro_rules! impl_tuples { 6 | ($num:expr, $( $ty:ident : $rt:ident : $var:ident : $no:tt ),+ $(,)?) => { 7 | impl<$( $ty, $rt ),+> CairoType for ($( $ty, )+) 8 | where 9 | $($ty: CairoType,)+ 10 | { 11 | type RustType = ($( $rt ),*); 12 | 13 | const SERIALIZED_SIZE: Option = None; 14 | 15 | #[inline] 16 | fn serialized_size(rust: &Self::RustType) -> usize { 17 | let mut size = 0; 18 | $( 19 | size += $ty::serialized_size(& rust.$no); 20 | )* 21 | 22 | size 23 | } 24 | 25 | fn serialize(rust: &Self::RustType) -> Vec { 26 | let mut out: Vec = vec![]; 27 | 28 | $( out.extend($ty::serialize(& rust.$no)); )* 29 | 30 | out 31 | } 32 | 33 | fn deserialize(felts: &[FieldElement], offset: usize) -> Result { 34 | let mut offset = offset; 35 | 36 | $( 37 | let $var : $rt = $ty::deserialize(felts, offset)?; 38 | offset += $ty::serialized_size(& $var); 39 | )* 40 | 41 | // Remove warning. 42 | let _offset = offset; 43 | 44 | Ok(($( $var ),*)) 45 | } 46 | } 47 | } 48 | } 49 | 50 | impl_tuples!(2, A:RA:r0:0, B:RB:r1:1); 51 | impl_tuples!(3, A:RA:r0:0, B:RB:r1:1, C:RC:r2:2); 52 | impl_tuples!(4, A:RA:r0:0, B:RB:r1:1, C:RC:r2:2, D:RD:r3:3); 53 | impl_tuples!(5, A:RA:r0:0, B:RB:r1:1, C:RC:r2:2, D:RD:r3:3, E:RE:r4:4); 54 | 55 | #[cfg(test)] 56 | mod tests { 57 | use super::*; 58 | 59 | #[test] 60 | fn test_serialize_tuple2() { 61 | let v = (FieldElement::ONE, 128_u32); 62 | let felts = <(FieldElement, u32)>::serialize(&v); 63 | assert_eq!(felts.len(), 2); 64 | assert_eq!(felts[0], FieldElement::ONE); 65 | assert_eq!(felts[1], FieldElement::from(128_u32)); 66 | } 67 | 68 | #[test] 69 | fn test_deserialize_tuple2() { 70 | let felts = vec![FieldElement::THREE, 99_u32.into()]; 71 | let vals = <(FieldElement, u32)>::deserialize(&felts, 0).unwrap(); 72 | assert_eq!(vals.0, FieldElement::THREE); 73 | assert_eq!(vals.1, 99_u32); 74 | } 75 | 76 | #[test] 77 | fn test_serialize_tuple2_array() { 78 | let v = (vec![FieldElement::ONE], 128_u32); 79 | let felts = <(Vec, u32)>::serialize(&v); 80 | assert_eq!(felts.len(), 3); 81 | assert_eq!(felts[0], FieldElement::ONE); 82 | assert_eq!(felts[1], FieldElement::ONE); 83 | assert_eq!(felts[2], FieldElement::from(128_u32)); 84 | } 85 | 86 | #[test] 87 | fn test_deserialize_tuple2_array() { 88 | let felts = vec![FieldElement::ONE, FieldElement::ONE, 99_u32.into()]; 89 | let vals = <(Vec, u32)>::deserialize(&felts, 0).unwrap(); 90 | assert_eq!(vals.0, vec![FieldElement::ONE]); 91 | assert_eq!(vals.1, 99_u32); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /crates/parser/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crates is about parsing Cairo types from an ABI. 2 | //! Later, this will also be able to parse Cairo type from Cairo code. 3 | //! 4 | //! The important consideration are the generic type. Indeed, in the ABI 5 | //! there is no information about a type genericity and how exactly 6 | //! the members/variants are following the generic type as everything is 7 | //! flattened. 8 | //! 9 | //! `abi_types` is the low level parsing of the types. It supports 10 | //! nested types. 11 | //! 12 | //! `CairoStruct`, `CairoEnum` and `CairoFunction` are higher level 13 | //! types to resolve the genericity and manage members/variants/inputs/outputs 14 | //! for simpler expansion. 15 | pub mod abi_types; 16 | 17 | mod cairo_struct; 18 | pub use cairo_struct::CairoStruct; 19 | 20 | mod cairo_enum; 21 | pub use cairo_enum::CairoEnum; 22 | 23 | mod cairo_function; 24 | pub use cairo_function::CairoFunction; 25 | 26 | mod cairo_event; 27 | pub use cairo_event::{CairoEvent, CairoEventInner}; 28 | 29 | pub mod cairo_types; 30 | pub use cairo_types::CairoType; 31 | -------------------------------------------------------------------------------- /examples/simple_get_set.rs: -------------------------------------------------------------------------------- 1 | use starknet::{ 2 | accounts::{Account, ConnectedAccount, ExecutionEncoding, SingleOwnerAccount}, 3 | core::types::FieldElement, 4 | providers::{jsonrpc::HttpTransport, AnyProvider, JsonRpcClient}, 5 | signers::{LocalWallet, SigningKey}, 6 | }; 7 | use starknet_abigen::macros::abigen; 8 | use std::sync::Arc; 9 | use url::Url; 10 | 11 | // Generate the bindings for the contract and also includes 12 | // all the structs and enums present in the ABI with the exact 13 | // same name. 14 | // It's usually a good idea to place the macro call into a separate module 15 | // to avoid name clashes. 16 | abigen!(MyContract, "./contracts/abi/simple_get_set.json"); 17 | 18 | #[tokio::main] 19 | async fn main() { 20 | let rpc_url = Url::parse("http://0.0.0.0:5050").expect("Expecting Starknet RPC URL"); 21 | let provider = 22 | AnyProvider::JsonRpcHttp(JsonRpcClient::new(HttpTransport::new(rpc_url.clone()))); 23 | 24 | let contract_address = FieldElement::from_hex_be( 25 | "0x02a293bd31c51a9d3b9ca52bceb77d920523a1cfdde513a95e2a5e792f6e85da", 26 | ) 27 | .unwrap(); 28 | 29 | // If you only plan to call views functions, you can use the `Reader`, which 30 | // only requires a provider along with your contract address. 31 | let contract = MyContractReader::new(contract_address, &provider); 32 | 33 | // To call a view, there is no need to initialize an account. You can directly 34 | // use the name of the method in the ABI to realize the call. 35 | let a = contract.get_a().await.expect("Call to `get_a` failed"); 36 | 37 | println!("a = {:?}", a); 38 | 39 | let b = contract.get_b().await.expect("Call to `get_b` failed"); 40 | 41 | println!("b = {:?}", b); 42 | 43 | // For the inputs / outputs of the ABI functions, all the types are 44 | // defined where the abigen macro is expanded. Consider using the macro abigen 45 | // in a separate module to avoid clashes. 46 | 47 | // If you want to do some invoke for external functions, you must use an account. 48 | let signer = LocalWallet::from(SigningKey::from_secret_scalar( 49 | FieldElement::from_hex_be("0x1800000000300000180000000000030000000000003006001800006600") 50 | .unwrap(), 51 | )); 52 | let address = FieldElement::from_hex_be( 53 | "0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973", 54 | ) 55 | .unwrap(); 56 | 57 | let account = Arc::new(SingleOwnerAccount::new( 58 | provider, 59 | signer, 60 | address, 61 | FieldElement::from_hex_be("0x4b4154414e41").unwrap(), // KATANA 62 | ExecutionEncoding::Legacy, 63 | )); 64 | 65 | let contract = MyContract::new(contract_address, account); 66 | 67 | let r = contract 68 | .set_a(&(a + FieldElement::ONE)) 69 | .await 70 | .expect("Call to `set_a` failed"); 71 | 72 | // Create a new reader from contract account. 73 | let reader = contract.reader(); 74 | 75 | loop { 76 | match reader.get_tx_status(r.transaction_hash).await.as_ref() { 77 | "ok" => break, 78 | "pending" => tokio::time::sleep(tokio::time::Duration::from_secs(1)).await, 79 | e => { 80 | println!("Transaction error: {e}"); 81 | break; 82 | } 83 | } 84 | } 85 | 86 | let a = reader.get_a().await.expect("Call to `get_a` failed"); 87 | 88 | println!("a = {:?}", a); 89 | 90 | // Now let's say we want to do multicall, and in one transaction we want to set a and b. 91 | let call_set_a = contract.set_a_getcall(&FieldElement::from_hex_be("0xee").unwrap()); 92 | let call_set_b = contract.set_b_getcall(&u256 { low: 0xff, high: 0 }); 93 | 94 | let r = contract 95 | .account 96 | .execute(vec![call_set_a, call_set_b]) 97 | .send() 98 | .await 99 | .expect("Multicall failed"); 100 | 101 | loop { 102 | match reader.get_tx_status(r.transaction_hash).await.as_ref() { 103 | "ok" => break, 104 | "pending" => tokio::time::sleep(tokio::time::Duration::from_secs(1)).await, 105 | e => { 106 | println!("Transaction error: {e}"); 107 | break; 108 | } 109 | } 110 | } 111 | 112 | let a = reader.get_a().await.expect("Call to `get_a` failed"); 113 | 114 | println!("a = {:?}", a); 115 | 116 | let b = reader.get_b().await.expect("Call to `get_b` failed"); 117 | 118 | println!("b = {:?}", b); 119 | 120 | let arc_contract = Arc::new(contract); 121 | 122 | let handle = tokio::spawn(async move { 123 | other_func(arc_contract.clone()).await; 124 | }); 125 | 126 | handle.await.unwrap(); 127 | } 128 | 129 | async fn other_func(contract: Arc>) { 130 | // As `Arc>` is also implementing `ConnectedAccount`, 131 | // passing a contract you also have the reader that you can retrieve anytime 132 | // by calling `contract.reader()`. 133 | let set_b = contract 134 | .set_b(&u256 { 135 | low: 0x1234, 136 | high: 0, 137 | }) 138 | .await 139 | .expect("Call to `set_b` failed"); 140 | 141 | let reader = contract.reader(); 142 | 143 | loop { 144 | match reader.get_tx_status(set_b.transaction_hash).await.as_ref() { 145 | "ok" => break, 146 | "pending" => tokio::time::sleep(tokio::time::Duration::from_secs(1)).await, 147 | e => { 148 | println!("Transaction error: {e}"); 149 | break; 150 | } 151 | } 152 | } 153 | 154 | let b = reader.get_b().await.expect("Call to `get_b` failed"); 155 | println!("b = {:?}", b); 156 | } 157 | -------------------------------------------------------------------------------- /scripts/clippy.sh: -------------------------------------------------------------------------------- 1 | cargo clippy --all-targets -- -D warnings 2 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Starknet abigen full crate. 2 | 3 | pub mod parser { 4 | pub use starknet_abigen_parser::*; 5 | } 6 | 7 | pub mod macros { 8 | pub use starknet_abigen_macros::*; 9 | } 10 | -------------------------------------------------------------------------------- /test.abi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "function", 4 | "name": "get_val", 5 | "inputs": [], 6 | "outputs": [ 7 | { 8 | "type": "core::felt252" 9 | } 10 | ], 11 | "state_mutability": "view" 12 | }, 13 | { 14 | "type": "function", 15 | "name": "set_val", 16 | "inputs": [ 17 | { 18 | "name": "v", 19 | "type": "core::felt252" 20 | } 21 | ], 22 | "outputs": [], 23 | "state_mutability": "external" 24 | }, 25 | { 26 | "type": "struct", 27 | "name": "contracts::c1::PG", 28 | "members": [ 29 | { 30 | "name": "v1", 31 | "type": "core::felt252" 32 | }, 33 | { 34 | "name": "v2", 35 | "type": "core::integer::u128" 36 | } 37 | ] 38 | }, 39 | { 40 | "type": "function", 41 | "name": "hello_world", 42 | "inputs": [ 43 | { 44 | "name": "value", 45 | "type": "core::felt252" 46 | } 47 | ], 48 | "outputs": [ 49 | { 50 | "type": "contracts::c1::PG" 51 | } 52 | ], 53 | "state_mutability": "view" 54 | }, 55 | { 56 | "type": "struct", 57 | "name": "core::integer::u256", 58 | "members": [ 59 | { 60 | "name": "low", 61 | "type": "core::integer::u128" 62 | }, 63 | { 64 | "name": "high", 65 | "type": "core::integer::u128" 66 | } 67 | ] 68 | }, 69 | { 70 | "type": "struct", 71 | "name": "contracts::c1::InnerOne", 72 | "members": [ 73 | { 74 | "name": "a", 75 | "type": "core::integer::u256" 76 | }, 77 | { 78 | "name": "b", 79 | "type": "core::array::Array::" 80 | } 81 | ] 82 | }, 83 | { 84 | "type": "struct", 85 | "name": "contracts::c1::ComplexOne", 86 | "members": [ 87 | { 88 | "name": "pg", 89 | "type": "contracts::c1::PG" 90 | }, 91 | { 92 | "name": "inner", 93 | "type": "contracts::c1::InnerOne" 94 | } 95 | ] 96 | }, 97 | { 98 | "type": "function", 99 | "name": "call_test", 100 | "inputs": [ 101 | { 102 | "name": "pg", 103 | "type": "contracts::c1::PG" 104 | }, 105 | { 106 | "name": "inner", 107 | "type": "contracts::c1::InnerOne" 108 | } 109 | ], 110 | "outputs": [ 111 | { 112 | "type": "contracts::c1::ComplexOne" 113 | } 114 | ], 115 | "state_mutability": "external" 116 | }, 117 | { 118 | "type": "struct", 119 | "name": "contracts::c1::TypesInTypes::", 120 | "members": [ 121 | { 122 | "name": "a", 123 | "type": "core::array::Array::>" 124 | }, 125 | { 126 | "name": "b", 127 | "type": "core::integer::u256" 128 | } 129 | ] 130 | }, 131 | { 132 | "type": "function", 133 | "name": "call_bla", 134 | "inputs": [ 135 | { 136 | "name": "a", 137 | "type": "contracts::c1::TypesInTypes::" 138 | } 139 | ], 140 | "outputs": [ 141 | { 142 | "type": "contracts::c1::TypesInTypes::" 143 | } 144 | ], 145 | "state_mutability": "external" 146 | }, 147 | { 148 | "type": "struct", 149 | "name": "core::array::Span::", 150 | "members": [ 151 | { 152 | "name": "snapshot", 153 | "type": "@core::array::Array::" 154 | } 155 | ] 156 | }, 157 | { 158 | "type": "function", 159 | "name": "call_bou", 160 | "inputs": [], 161 | "outputs": [ 162 | { 163 | "type": "(core::array::Span::, core::felt252)" 164 | } 165 | ], 166 | "state_mutability": "external" 167 | }, 168 | { 169 | "type": "struct", 170 | "name": "core::array::Span::<(core::felt252, contracts::c1::PG)>", 171 | "members": [ 172 | { 173 | "name": "snapshot", 174 | "type": "@core::array::Array::<(core::felt252, contracts::c1::PG)>" 175 | } 176 | ] 177 | }, 178 | { 179 | "type": "function", 180 | "name": "call_bii", 181 | "inputs": [], 182 | "outputs": [ 183 | { 184 | "type": "(core::array::Span::<(core::felt252, contracts::c1::PG)>, core::felt252)" 185 | } 186 | ], 187 | "state_mutability": "external" 188 | }, 189 | { 190 | "type": "enum", 191 | "name": "contracts::c1::TestEnum::", 192 | "variants": [ 193 | { 194 | "name": "V1", 195 | "type": "core::felt252" 196 | }, 197 | { 198 | "name": "V2", 199 | "type": "core::integer::u128" 200 | }, 201 | { 202 | "name": "V3", 203 | "type": "core::array::Array::" 204 | }, 205 | { 206 | "name": "V4", 207 | "type": "()" 208 | } 209 | ] 210 | }, 211 | { 212 | "type": "function", 213 | "name": "call_baba", 214 | "inputs": [], 215 | "outputs": [ 216 | { 217 | "type": "contracts::c1::TestEnum::" 218 | } 219 | ], 220 | "state_mutability": "external" 221 | }, 222 | { 223 | "type": "function", 224 | "name": "call_baba2", 225 | "inputs": [], 226 | "outputs": [ 227 | { 228 | "type": "contracts::c1::TestEnum::" 229 | } 230 | ], 231 | "state_mutability": "external" 232 | }, 233 | { 234 | "type": "function", 235 | "name": "call_pia", 236 | "inputs": [], 237 | "outputs": [ 238 | { 239 | "type": "contracts::c1::TestEnum::" 240 | } 241 | ], 242 | "state_mutability": "external" 243 | }, 244 | { 245 | "type": "enum", 246 | "name": "core::option::Option::", 247 | "variants": [ 248 | { 249 | "name": "Some", 250 | "type": "core::felt252" 251 | }, 252 | { 253 | "name": "None", 254 | "type": "()" 255 | } 256 | ] 257 | }, 258 | { 259 | "type": "function", 260 | "name": "call_bibi", 261 | "inputs": [], 262 | "outputs": [ 263 | { 264 | "type": "core::option::Option::" 265 | } 266 | ], 267 | "state_mutability": "external" 268 | }, 269 | { 270 | "type": "function", 271 | "name": "call_bobo", 272 | "inputs": [], 273 | "outputs": [ 274 | { 275 | "type": "core::option::Option::" 276 | } 277 | ], 278 | "state_mutability": "external" 279 | }, 280 | { 281 | "type": "function", 282 | "name": "call_bok", 283 | "inputs": [], 284 | "outputs": [ 285 | { 286 | "type": "(core::felt252, core::felt252)" 287 | } 288 | ], 289 | "state_mutability": "external" 290 | }, 291 | { 292 | "type": "event", 293 | "name": "contracts::c1::Event", 294 | "kind": "enum", 295 | "variants": [] 296 | } 297 | ] 298 | --------------------------------------------------------------------------------