casthon-sdk 0.1.0a1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. casthon_sdk-0.1.0a1/PKG-INFO +221 -0
  2. casthon_sdk-0.1.0a1/README.md +199 -0
  3. casthon_sdk-0.1.0a1/pyproject.toml +46 -0
  4. casthon_sdk-0.1.0a1/setup.cfg +4 -0
  5. casthon_sdk-0.1.0a1/src/casthon/__init__.py +116 -0
  6. casthon_sdk-0.1.0a1/src/casthon/_cl_types.py +104 -0
  7. casthon_sdk-0.1.0a1/src/casthon/_context.py +15 -0
  8. casthon_sdk-0.1.0a1/src/casthon/_contract_schema.py +153 -0
  9. casthon_sdk-0.1.0a1/src/casthon/_dispatch.py +26 -0
  10. casthon_sdk-0.1.0a1/src/casthon/_entry_point_signature.py +58 -0
  11. casthon_sdk-0.1.0a1/src/casthon/_event_schema.py +78 -0
  12. casthon_sdk-0.1.0a1/src/casthon/_ffi.py +12 -0
  13. casthon_sdk-0.1.0a1/src/casthon/_signatures.py +134 -0
  14. casthon_sdk-0.1.0a1/src/casthon/_wire_types.py +87 -0
  15. casthon_sdk-0.1.0a1/src/casthon/api_error.py +15 -0
  16. casthon_sdk-0.1.0a1/src/casthon/arguments.py +45 -0
  17. casthon_sdk-0.1.0a1/src/casthon/bytesrepr.py +56 -0
  18. casthon_sdk-0.1.0a1/src/casthon/caller.py +125 -0
  19. casthon_sdk-0.1.0a1/src/casthon/ces.py +48 -0
  20. casthon_sdk-0.1.0a1/src/casthon/codec.py +242 -0
  21. casthon_sdk-0.1.0a1/src/casthon/contract_api/__init__.py +1 -0
  22. casthon_sdk-0.1.0a1/src/casthon/contract_api/account.py +35 -0
  23. casthon_sdk-0.1.0a1/src/casthon/contract_api/cryptography.py +22 -0
  24. casthon_sdk-0.1.0a1/src/casthon/contract_api/runtime.py +324 -0
  25. casthon_sdk-0.1.0a1/src/casthon/contract_api/storage.py +195 -0
  26. casthon_sdk-0.1.0a1/src/casthon/contract_api/system.py +77 -0
  27. casthon_sdk-0.1.0a1/src/casthon/contract_call.py +42 -0
  28. casthon_sdk-0.1.0a1/src/casthon/contracts.py +255 -0
  29. casthon_sdk-0.1.0a1/src/casthon/crypto.py +70 -0
  30. casthon_sdk-0.1.0a1/src/casthon/entry_points.py +85 -0
  31. casthon_sdk-0.1.0a1/src/casthon/enums.py +96 -0
  32. casthon_sdk-0.1.0a1/src/casthon/errors.py +96 -0
  33. casthon_sdk-0.1.0a1/src/casthon/events.py +222 -0
  34. casthon_sdk-0.1.0a1/src/casthon/key.py +545 -0
  35. casthon_sdk-0.1.0a1/src/casthon/messages.py +24 -0
  36. casthon_sdk-0.1.0a1/src/casthon/py.typed +0 -0
  37. casthon_sdk-0.1.0a1/src/casthon/references.py +250 -0
  38. casthon_sdk-0.1.0a1/src/casthon/return_value.py +79 -0
  39. casthon_sdk-0.1.0a1/src/casthon/testing/__init__.py +64 -0
  40. casthon_sdk-0.1.0a1/src/casthon/testing/build.py +120 -0
  41. casthon_sdk-0.1.0a1/src/casthon/testing/contract.py +120 -0
  42. casthon_sdk-0.1.0a1/src/casthon/testing/devnet.py +579 -0
  43. casthon_sdk-0.1.0a1/src/casthon/testing/live.py +87 -0
  44. casthon_sdk-0.1.0a1/src/casthon/testing/mock.py +453 -0
  45. casthon_sdk-0.1.0a1/src/casthon/testing/prints.py +80 -0
  46. casthon_sdk-0.1.0a1/src/casthon/testing/values.py +116 -0
  47. casthon_sdk-0.1.0a1/src/casthon/types.py +54 -0
  48. casthon_sdk-0.1.0a1/src/casthon/values.py +110 -0
  49. casthon_sdk-0.1.0a1/src/casthon_sdk.egg-info/PKG-INFO +221 -0
  50. casthon_sdk-0.1.0a1/src/casthon_sdk.egg-info/SOURCES.txt +75 -0
  51. casthon_sdk-0.1.0a1/src/casthon_sdk.egg-info/dependency_links.txt +1 -0
  52. casthon_sdk-0.1.0a1/src/casthon_sdk.egg-info/requires.txt +8 -0
  53. casthon_sdk-0.1.0a1/src/casthon_sdk.egg-info/top_level.txt +1 -0
  54. casthon_sdk-0.1.0a1/tests/test_argument_loading.py +95 -0
  55. casthon_sdk-0.1.0a1/tests/test_build_cache.py +82 -0
  56. casthon_sdk-0.1.0a1/tests/test_build_progress.py +49 -0
  57. casthon_sdk-0.1.0a1/tests/test_cep18.py +422 -0
  58. casthon_sdk-0.1.0a1/tests/test_cep78.py +527 -0
  59. casthon_sdk-0.1.0a1/tests/test_class_contracts.py +511 -0
  60. casthon_sdk-0.1.0a1/tests/test_contract_api.py +373 -0
  61. casthon_sdk-0.1.0a1/tests/test_contract_calls.py +196 -0
  62. casthon_sdk-0.1.0a1/tests/test_contract_packages.py +115 -0
  63. casthon_sdk-0.1.0a1/tests/test_contract_testing.py +234 -0
  64. casthon_sdk-0.1.0a1/tests/test_devnet_testing.py +292 -0
  65. casthon_sdk-0.1.0a1/tests/test_entry_point_factory.py +205 -0
  66. casthon_sdk-0.1.0a1/tests/test_enum_types.py +147 -0
  67. casthon_sdk-0.1.0a1/tests/test_events.py +396 -0
  68. casthon_sdk-0.1.0a1/tests/test_extended_api.py +321 -0
  69. casthon_sdk-0.1.0a1/tests/test_ffi_boundary.py +140 -0
  70. casthon_sdk-0.1.0a1/tests/test_named_keys.py +178 -0
  71. casthon_sdk-0.1.0a1/tests/test_native_unwind.py +29 -0
  72. casthon_sdk-0.1.0a1/tests/test_optional_args.py +186 -0
  73. casthon_sdk-0.1.0a1/tests/test_print_capture.py +84 -0
  74. casthon_sdk-0.1.0a1/tests/test_references.py +312 -0
  75. casthon_sdk-0.1.0a1/tests/test_return_values.py +103 -0
  76. casthon_sdk-0.1.0a1/tests/test_sdk.py +304 -0
  77. casthon_sdk-0.1.0a1/tests/test_typed_exports.py +237 -0
@@ -0,0 +1,221 @@
1
+ Metadata-Version: 2.4
2
+ Name: casthon-sdk
3
+ Version: 0.1.0a1
4
+ Summary: Python SDK for writing Casper smart contracts with Casthon
5
+ Project-URL: Repository, https://github.com/veles-labs/casthon
6
+ Project-URL: Issues, https://github.com/veles-labs/casthon/issues
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Programming Language :: Python :: 3.14
14
+ Classifier: Typing :: Typed
15
+ Requires-Python: >=3.11
16
+ Description-Content-Type: text/markdown
17
+ Requires-Dist: typing_extensions>=4.12; python_version < "3.12"
18
+ Provides-Extra: dev
19
+ Requires-Dist: mypy==1.19.1; extra == "dev"
20
+ Requires-Dist: wasmtime==49.0.0; extra == "dev"
21
+ Requires-Dist: pytest==9.1.1; extra == "dev"
22
+
23
+ <p align="center">
24
+ <img src="docs/assets/casthon-logo.png" width="176" alt="A friendly ghost with a curled tail, inspired by the snake emoji">
25
+ </p>
26
+
27
+ # Casthon
28
+
29
+ **Python smart contracts for Casper.**
30
+
31
+ Write a contract as a Python class, give its entrypoints type annotations, and
32
+ build it into Casper-compatible WebAssembly. Casthon freezes your code with
33
+ MicroPython; the `casthon` SDK handles arguments, storage, return values and host calls.
34
+
35
+ The same contract source runs in ordinary Python tests. You can compose classes,
36
+ catch exceptions, and use `finally` for cleanup. An uncaught contract error becomes
37
+ a Casper revert at the outer invocation boundary.
38
+
39
+ ## Hello, Casper
40
+
41
+ Here is [hello.py](contracts/hello.py), including its installer:
42
+
43
+ ```python
44
+ from casthon import ContractHash, ContractPackageHash, U32, URef, UserError, export
45
+ from casthon.contract_api import storage
46
+ from casthon.contract_api.runtime import named_keys
47
+ from casthon.contracts import Contract, entry_point, export_contract
48
+
49
+
50
+ @export_contract
51
+ class Hello(Contract):
52
+ calls: URef[U32]
53
+
54
+ @entry_point
55
+ def greet(self, name: str) -> str:
56
+ try:
57
+ name = name.strip()
58
+ if not name:
59
+ raise ValueError("Empty name")
60
+ if len(name) > 64:
61
+ raise UserError(1)
62
+ return f"Hello, {name}!"
63
+ except ValueError:
64
+ return "Hello, stranger!"
65
+ finally:
66
+ self.calls.write(self.calls.read() + 1)
67
+ print("Greeting finished")
68
+
69
+
70
+ installed = named_keys.reference("hello", ContractHash)
71
+ installed_package = named_keys.reference("hello_package", ContractPackageHash)
72
+ installed_access = named_keys.reference("hello_access", URef[None])
73
+
74
+
75
+ @export
76
+ def call() -> None:
77
+ package, access = storage.create_contract_package_at_hash()
78
+ contract, _ = package.add_contract_version(Hello(calls=0))
79
+ installed.set(contract)
80
+ installed_package.set(package)
81
+ installed_access.set(access)
82
+ ```
83
+
84
+ `greet` receives and returns a Python string. `calls` is persistent U32 state.
85
+ An empty name takes the `except` branch. A name longer than 64 characters raises
86
+ a user error and reverts. The `finally` block runs in either case; a revert rolls
87
+ back its storage write too.
88
+
89
+ `@entry_point` describes the class ABI. `@export_contract` selects its Wasm
90
+ exports. The top-level `call` creates the package and stores the deployed hashes
91
+ explicitly. [The Python API guide](docs/python-api.md) explains the boundaries.
92
+
93
+ ## Try it
94
+
95
+ This developer preview builds from a source checkout. You need Python 3.11+,
96
+ `uv`, Rust/Cargo, Node, Make, a host C compiler, Emscripten and Binaryen.
97
+ The [CLI guide](docs/casthon.md#requirements) covers the build requirements.
98
+
99
+ ```sh
100
+ make setup
101
+ make cli
102
+ ./target/release/casthon doctor
103
+ ./target/release/casthon test -q
104
+ ./target/release/casthon build hello
105
+ ./target/release/casthon inspect build/hello.wasm
106
+ ```
107
+
108
+ The build produces `build/hello.wasm`, its metered test counterpart, and a build
109
+ report. It compiles the contract; deployment is a separate transaction.
110
+
111
+ Debug builds keep `print()` and exception tracebacks. For a release build:
112
+
113
+ ```sh
114
+ ./target/release/casthon build hello --release
115
+ ```
116
+
117
+ That writes `build/release/hello.wasm` and removes the `casper_print` import.
118
+ See [CLI usage](docs/casthon.md) for project configuration, templates, caching
119
+ and minification.
120
+
121
+ ## A few more examples
122
+
123
+ - [Counter](contracts/counter.py) composes ownership and pause controls, with a
124
+ typed URef and a persistent dictionary.
125
+ - [Counter session](contracts/counter_session.py) imports the counter's Python
126
+ ABI to call a deployed contract. The session exports only `call`.
127
+ - [MyToken](contracts/my_token.py) inherits the [CEP-18 base](contracts/cep18.py),
128
+ including its entrypoints, permissions and typed events.
129
+ - [MyNft](contracts/my_nft.py) inherits the [CEP-78 base](contracts/cep78.py),
130
+ with NFT metadata, approvals, ownership receipts and typed events.
131
+ - [Basic](contracts/basic.py) shows the procedural `contract_api` beneath the
132
+ class API.
133
+
134
+ ## Testing
135
+
136
+ Use pytest or unittest with `MockHost` for fast feedback. `ContractHarness` adds
137
+ argument decoding, contract context, return encoding and transaction rollback.
138
+ Compiled tests exercise the actual MicroPython/C adapter in Wasmtime. Live tests
139
+ start and stop their own Casper devnets.
140
+
141
+ ```sh
142
+ make test # Native Python tests
143
+ make typecheck
144
+ make build # Example Wasm artifacts needed by the compiled suite
145
+ make test-wasm
146
+ make test-live # Starts local Casper networks; requires casper-devnet
147
+ ```
148
+
149
+ `casthon test` is a pytest wrapper. Wasm and live tests are ordinary test suites,
150
+ with the live opt-in described in the [testing guide](docs/testing.md).
151
+
152
+ ## Tooling benchmarks
153
+
154
+ Matching [counter contracts and six tests](benchmarks/tooling/), measured on
155
+ 2026-10-22 with an Apple M1 Max (10 cores, 64 GB RAM, macOS 27.0).
156
+ Times are **median wall-clock seconds across three runs**, including command
157
+ startup. The comparison uses Casthon 0.1.0, Odra 2.9.1 and cargo-odra 0.1.8.
158
+
159
+ | Workflow | Casthon (seconds) | Odra (seconds) |
160
+ | --- | ---: | ---: |
161
+ | Clean release build | 44.59 | 26.20 |
162
+ | First unchanged build after clean | 15.98 | 0.40 |
163
+ | Cached unchanged build | 1.19 | 0.30 |
164
+ | Incremental build after a source edit | 5.14 | 2.57 |
165
+ | Cold check / Clippy | 0.92 | 33.74 |
166
+ | Cached unchanged check | 0.17 | 0.28 |
167
+ | Incremental check after a source edit | 0.28 | 0.29 |
168
+ | Cold tests | 0.36 | 153.20 |
169
+ | Warm unchanged tests | 0.18 | 0.43 |
170
+ | Tests after a source edit | 0.19 | 1.05 |
171
+
172
+ Clean/cold runs clear compilation or checker caches; tool installation and
173
+ downloads are excluded. Incremental runs change an entrypoint's return value
174
+ from `1` to `2`. Builds use each tool's standard release pipeline; Casthon also
175
+ produces validated, metered Wasm. Checks compare `casthon check contracts` with
176
+ default-target `cargo clippy --locked -- -D warnings`. Tests run the six matching
177
+ cases through Casthon's native `MockHost`/`ContractHarness` and OdraVM, including
178
+ any required test compilation.
179
+
180
+ The first unchanged Casthon build in this snapshot recompiles the runtime:
181
+ Emscripten's one-time sanity-check message changes the compiler cache key.
182
+ Later unchanged builds hit the cache. Other sessions were active and OS file
183
+ caches were retained, so these figures are indicative; Odra's cold test runs
184
+ ranged from 150 to 251 seconds.
185
+
186
+ To repeat the comparison, install cargo-odra 0.1.8 and the required toolchains,
187
+ then run the [benchmark script](scripts/benchmark_tooling.py):
188
+
189
+ ```sh
190
+ .venv/bin/python scripts/benchmark_tooling.py --repeats 3
191
+ ```
192
+
193
+ The runner isolates its builds and defaults to saving reports and raw logs under
194
+ the ignored `.experiments/tooling-benchmarks/` directory. Use
195
+ `--clippy-all-targets` to include test targets in the Clippy comparison.
196
+
197
+ ## Read more
198
+
199
+ | Guide | Contents |
200
+ | --- | --- |
201
+ | [CLI](docs/casthon.md) | Setup, configuration, commands and artifacts |
202
+ | [Python API](docs/python-api.md) | Classes, typed arguments, state, events and low-level calls |
203
+ | [Testing](docs/testing.md) | Mock state, invocation boundaries and live networks |
204
+ | [CEP-18](docs/cep18.md) | Token implementation, event formats and upgrades |
205
+ | [CEP-78](docs/cep78.md) | NFT collections, metadata, approvals, receipts and Python upgrades |
206
+ | [Repository and architecture](docs/architecture.md) | Code ownership, build stages and the remaining 0.1 work |
207
+ | [Short pitch](docs/pitch.md) | A brief introduction and a small demo |
208
+ | [Native port](native/README.md) | Interpreter lifecycle, unwinding and host completion |
209
+
210
+ ## Status
211
+
212
+ Casthon is a developer preview. The CLI and SDK live in one repository; the CLI
213
+ still needs this checkout's compiler and runtime resources. Standalone packaging
214
+ and selecting an installed project SDK are the next release work.
215
+
216
+ The default runtime is pristine MicroPython 1.29.0 with Asyncify, software binary64
217
+ floats, and fixed 4 MiB linear memory with GC compiled out. The preserved `native-errors`
218
+ backend remains available. Supported Python constructs and runtime limits are
219
+ listed in the [architecture guide](docs/architecture.md#runtime-profile).
220
+
221
+ [Logo credits](docs/assets/README.md).
@@ -0,0 +1,199 @@
1
+ <p align="center">
2
+ <img src="docs/assets/casthon-logo.png" width="176" alt="A friendly ghost with a curled tail, inspired by the snake emoji">
3
+ </p>
4
+
5
+ # Casthon
6
+
7
+ **Python smart contracts for Casper.**
8
+
9
+ Write a contract as a Python class, give its entrypoints type annotations, and
10
+ build it into Casper-compatible WebAssembly. Casthon freezes your code with
11
+ MicroPython; the `casthon` SDK handles arguments, storage, return values and host calls.
12
+
13
+ The same contract source runs in ordinary Python tests. You can compose classes,
14
+ catch exceptions, and use `finally` for cleanup. An uncaught contract error becomes
15
+ a Casper revert at the outer invocation boundary.
16
+
17
+ ## Hello, Casper
18
+
19
+ Here is [hello.py](contracts/hello.py), including its installer:
20
+
21
+ ```python
22
+ from casthon import ContractHash, ContractPackageHash, U32, URef, UserError, export
23
+ from casthon.contract_api import storage
24
+ from casthon.contract_api.runtime import named_keys
25
+ from casthon.contracts import Contract, entry_point, export_contract
26
+
27
+
28
+ @export_contract
29
+ class Hello(Contract):
30
+ calls: URef[U32]
31
+
32
+ @entry_point
33
+ def greet(self, name: str) -> str:
34
+ try:
35
+ name = name.strip()
36
+ if not name:
37
+ raise ValueError("Empty name")
38
+ if len(name) > 64:
39
+ raise UserError(1)
40
+ return f"Hello, {name}!"
41
+ except ValueError:
42
+ return "Hello, stranger!"
43
+ finally:
44
+ self.calls.write(self.calls.read() + 1)
45
+ print("Greeting finished")
46
+
47
+
48
+ installed = named_keys.reference("hello", ContractHash)
49
+ installed_package = named_keys.reference("hello_package", ContractPackageHash)
50
+ installed_access = named_keys.reference("hello_access", URef[None])
51
+
52
+
53
+ @export
54
+ def call() -> None:
55
+ package, access = storage.create_contract_package_at_hash()
56
+ contract, _ = package.add_contract_version(Hello(calls=0))
57
+ installed.set(contract)
58
+ installed_package.set(package)
59
+ installed_access.set(access)
60
+ ```
61
+
62
+ `greet` receives and returns a Python string. `calls` is persistent U32 state.
63
+ An empty name takes the `except` branch. A name longer than 64 characters raises
64
+ a user error and reverts. The `finally` block runs in either case; a revert rolls
65
+ back its storage write too.
66
+
67
+ `@entry_point` describes the class ABI. `@export_contract` selects its Wasm
68
+ exports. The top-level `call` creates the package and stores the deployed hashes
69
+ explicitly. [The Python API guide](docs/python-api.md) explains the boundaries.
70
+
71
+ ## Try it
72
+
73
+ This developer preview builds from a source checkout. You need Python 3.11+,
74
+ `uv`, Rust/Cargo, Node, Make, a host C compiler, Emscripten and Binaryen.
75
+ The [CLI guide](docs/casthon.md#requirements) covers the build requirements.
76
+
77
+ ```sh
78
+ make setup
79
+ make cli
80
+ ./target/release/casthon doctor
81
+ ./target/release/casthon test -q
82
+ ./target/release/casthon build hello
83
+ ./target/release/casthon inspect build/hello.wasm
84
+ ```
85
+
86
+ The build produces `build/hello.wasm`, its metered test counterpart, and a build
87
+ report. It compiles the contract; deployment is a separate transaction.
88
+
89
+ Debug builds keep `print()` and exception tracebacks. For a release build:
90
+
91
+ ```sh
92
+ ./target/release/casthon build hello --release
93
+ ```
94
+
95
+ That writes `build/release/hello.wasm` and removes the `casper_print` import.
96
+ See [CLI usage](docs/casthon.md) for project configuration, templates, caching
97
+ and minification.
98
+
99
+ ## A few more examples
100
+
101
+ - [Counter](contracts/counter.py) composes ownership and pause controls, with a
102
+ typed URef and a persistent dictionary.
103
+ - [Counter session](contracts/counter_session.py) imports the counter's Python
104
+ ABI to call a deployed contract. The session exports only `call`.
105
+ - [MyToken](contracts/my_token.py) inherits the [CEP-18 base](contracts/cep18.py),
106
+ including its entrypoints, permissions and typed events.
107
+ - [MyNft](contracts/my_nft.py) inherits the [CEP-78 base](contracts/cep78.py),
108
+ with NFT metadata, approvals, ownership receipts and typed events.
109
+ - [Basic](contracts/basic.py) shows the procedural `contract_api` beneath the
110
+ class API.
111
+
112
+ ## Testing
113
+
114
+ Use pytest or unittest with `MockHost` for fast feedback. `ContractHarness` adds
115
+ argument decoding, contract context, return encoding and transaction rollback.
116
+ Compiled tests exercise the actual MicroPython/C adapter in Wasmtime. Live tests
117
+ start and stop their own Casper devnets.
118
+
119
+ ```sh
120
+ make test # Native Python tests
121
+ make typecheck
122
+ make build # Example Wasm artifacts needed by the compiled suite
123
+ make test-wasm
124
+ make test-live # Starts local Casper networks; requires casper-devnet
125
+ ```
126
+
127
+ `casthon test` is a pytest wrapper. Wasm and live tests are ordinary test suites,
128
+ with the live opt-in described in the [testing guide](docs/testing.md).
129
+
130
+ ## Tooling benchmarks
131
+
132
+ Matching [counter contracts and six tests](benchmarks/tooling/), measured on
133
+ 2026-10-22 with an Apple M1 Max (10 cores, 64 GB RAM, macOS 27.0).
134
+ Times are **median wall-clock seconds across three runs**, including command
135
+ startup. The comparison uses Casthon 0.1.0, Odra 2.9.1 and cargo-odra 0.1.8.
136
+
137
+ | Workflow | Casthon (seconds) | Odra (seconds) |
138
+ | --- | ---: | ---: |
139
+ | Clean release build | 44.59 | 26.20 |
140
+ | First unchanged build after clean | 15.98 | 0.40 |
141
+ | Cached unchanged build | 1.19 | 0.30 |
142
+ | Incremental build after a source edit | 5.14 | 2.57 |
143
+ | Cold check / Clippy | 0.92 | 33.74 |
144
+ | Cached unchanged check | 0.17 | 0.28 |
145
+ | Incremental check after a source edit | 0.28 | 0.29 |
146
+ | Cold tests | 0.36 | 153.20 |
147
+ | Warm unchanged tests | 0.18 | 0.43 |
148
+ | Tests after a source edit | 0.19 | 1.05 |
149
+
150
+ Clean/cold runs clear compilation or checker caches; tool installation and
151
+ downloads are excluded. Incremental runs change an entrypoint's return value
152
+ from `1` to `2`. Builds use each tool's standard release pipeline; Casthon also
153
+ produces validated, metered Wasm. Checks compare `casthon check contracts` with
154
+ default-target `cargo clippy --locked -- -D warnings`. Tests run the six matching
155
+ cases through Casthon's native `MockHost`/`ContractHarness` and OdraVM, including
156
+ any required test compilation.
157
+
158
+ The first unchanged Casthon build in this snapshot recompiles the runtime:
159
+ Emscripten's one-time sanity-check message changes the compiler cache key.
160
+ Later unchanged builds hit the cache. Other sessions were active and OS file
161
+ caches were retained, so these figures are indicative; Odra's cold test runs
162
+ ranged from 150 to 251 seconds.
163
+
164
+ To repeat the comparison, install cargo-odra 0.1.8 and the required toolchains,
165
+ then run the [benchmark script](scripts/benchmark_tooling.py):
166
+
167
+ ```sh
168
+ .venv/bin/python scripts/benchmark_tooling.py --repeats 3
169
+ ```
170
+
171
+ The runner isolates its builds and defaults to saving reports and raw logs under
172
+ the ignored `.experiments/tooling-benchmarks/` directory. Use
173
+ `--clippy-all-targets` to include test targets in the Clippy comparison.
174
+
175
+ ## Read more
176
+
177
+ | Guide | Contents |
178
+ | --- | --- |
179
+ | [CLI](docs/casthon.md) | Setup, configuration, commands and artifacts |
180
+ | [Python API](docs/python-api.md) | Classes, typed arguments, state, events and low-level calls |
181
+ | [Testing](docs/testing.md) | Mock state, invocation boundaries and live networks |
182
+ | [CEP-18](docs/cep18.md) | Token implementation, event formats and upgrades |
183
+ | [CEP-78](docs/cep78.md) | NFT collections, metadata, approvals, receipts and Python upgrades |
184
+ | [Repository and architecture](docs/architecture.md) | Code ownership, build stages and the remaining 0.1 work |
185
+ | [Short pitch](docs/pitch.md) | A brief introduction and a small demo |
186
+ | [Native port](native/README.md) | Interpreter lifecycle, unwinding and host completion |
187
+
188
+ ## Status
189
+
190
+ Casthon is a developer preview. The CLI and SDK live in one repository; the CLI
191
+ still needs this checkout's compiler and runtime resources. Standalone packaging
192
+ and selecting an installed project SDK are the next release work.
193
+
194
+ The default runtime is pristine MicroPython 1.29.0 with Asyncify, software binary64
195
+ floats, and fixed 4 MiB linear memory with GC compiled out. The preserved `native-errors`
196
+ backend remains available. Supported Python constructs and runtime limits are
197
+ listed in the [architecture guide](docs/architecture.md#runtime-profile).
198
+
199
+ [Logo credits](docs/assets/README.md).
@@ -0,0 +1,46 @@
1
+ [build-system]
2
+ requires = ["setuptools>=75"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "casthon-sdk"
7
+ version = "0.1.0a1"
8
+ description = "Python SDK for writing Casper smart contracts with Casthon"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ dependencies = ["typing_extensions>=4.12; python_version<'3.12'"]
12
+ classifiers = [
13
+ "Development Status :: 3 - Alpha",
14
+ "Intended Audience :: Developers",
15
+ "Programming Language :: Python :: 3",
16
+ "Programming Language :: Python :: 3.11",
17
+ "Programming Language :: Python :: 3.12",
18
+ "Programming Language :: Python :: 3.13",
19
+ "Programming Language :: Python :: 3.14",
20
+ "Typing :: Typed",
21
+ ]
22
+
23
+ [project.urls]
24
+ Repository = "https://github.com/veles-labs/casthon"
25
+ Issues = "https://github.com/veles-labs/casthon/issues"
26
+
27
+ [project.optional-dependencies]
28
+ dev = ["mypy==1.19.1", "wasmtime==49.0.0", "pytest==9.1.1"]
29
+
30
+ [tool.casthon]
31
+ micropython = "1.29.0"
32
+ targets = ["contracts/counter.py"]
33
+
34
+ [tool.pytest.ini_options]
35
+ testpaths = ["tests/test_*.py"]
36
+ pythonpath = ["src", "tests", "tests/wasm", "tests/fixtures"]
37
+ addopts = ["--import-mode=importlib"]
38
+
39
+ [tool.setuptools.packages.find]
40
+ where = ["src"]
41
+
42
+ [tool.mypy]
43
+ python_version = "3.11"
44
+ strict = true
45
+ mypy_path = "src"
46
+ files = ["contracts", "src/casthon"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,116 @@
1
+ """Casper data types; host functions live in casthon.contract_api."""
2
+
3
+ from typing import Any, Callable, TypeVar
4
+
5
+ from . import api_error as api_error
6
+
7
+ from .errors import (
8
+ Ret as Ret, Revert as Revert, ApiError as ApiError, User as User, UserError as UserError,
9
+ MissingKey as MissingKey, MissingArgument as MissingArgument,
10
+ InvalidArgument as InvalidArgument, InvalidPurse as InvalidPurse,
11
+ Read as Read, ValueNotFound as ValueNotFound,
12
+ DictionaryItemKeyExceedsLength as DictionaryItemKeyExceedsLength,
13
+ )
14
+ from .key import (
15
+ Key as Key,
16
+ AccountHash as AccountHash,
17
+ Hash as Hash,
18
+ ContractHash as ContractHash,
19
+ ContractPackageHash as ContractPackageHash,
20
+ URef as URef,
21
+ TransferHash as TransferHash,
22
+ DeployInfo as DeployInfo,
23
+ EraInfo as EraInfo,
24
+ Balance as Balance,
25
+ Bid as Bid,
26
+ Withdraw as Withdraw,
27
+ DictionaryAddress as DictionaryAddress,
28
+ SystemEntityRegistry as SystemEntityRegistry,
29
+ EraSummary as EraSummary,
30
+ Unbond as Unbond,
31
+ ChainspecRegistry as ChainspecRegistry,
32
+ ChecksumRegistry as ChecksumRegistry,
33
+ BidAddress as BidAddress,
34
+ PackageHash as PackageHash,
35
+ AddressableEntity as AddressableEntity,
36
+ SystemEntity as SystemEntity,
37
+ AccountEntity as AccountEntity,
38
+ ContractEntity as ContractEntity,
39
+ ByteCode as ByteCode,
40
+ Message as Message,
41
+ NamedKeyAddress as NamedKeyAddress,
42
+ BlockGlobal as BlockGlobal,
43
+ BalanceHold as BalanceHold,
44
+ EntryPointAddress as EntryPointAddress,
45
+ State as State,
46
+ base64 as base64,
47
+ )
48
+ from .enums import (
49
+ WireEnum as WireEnum,
50
+ Phase as Phase, ActionType as ActionType, AddKeyFailure as AddKeyFailure,
51
+ RemoveKeyFailure as RemoveKeyFailure, UpdateKeyFailure as UpdateKeyFailure,
52
+ SetThresholdFailure as SetThresholdFailure, SystemEntityType as SystemEntityType,
53
+ TransferredTo as TransferredTo, HashAlgorithm as HashAlgorithm,
54
+ MessageTopicOperation as MessageTopicOperation, EntryPointAccess as EntryPointAccess,
55
+ EntryPointType as EntryPointType, EntryPointPayment as EntryPointPayment,
56
+ )
57
+ from .types import (
58
+ Map as Map,
59
+ CLType as CLType,
60
+ cl_type_of as cl_type_of,
61
+ U8 as U8,
62
+ U32 as U32,
63
+ U64 as U64,
64
+ U256 as U256,
65
+ U512 as U512,
66
+ wire_type as wire_type,
67
+ )
68
+ from .codec import ABIError as ABIError
69
+
70
+ from .entry_points import EntryPoint as EntryPoint
71
+
72
+ from .values import (
73
+ CLValue as CLValue,
74
+ CLU8 as CLU8,
75
+ CLU32 as CLU32,
76
+ CLU64 as CLU64,
77
+ CLU256 as CLU256,
78
+ CLU512 as CLU512,
79
+ CLString as CLString,
80
+ CLBool as CLBool,
81
+ CLKey as CLKey,
82
+ CLKeyList as CLKeyList,
83
+ CLSecurityMap as CLSecurityMap,
84
+ CLBytes as CLBytes,
85
+ CLUnit as CLUnit,
86
+ SerializedCLValue as SerializedCLValue,
87
+ )
88
+
89
+ from .arguments import OptionalArg as OptionalArg, MISSING as MISSING, MissingType as MissingType
90
+ from .references import Dictionary as Dictionary
91
+ from .crypto import (
92
+ PublicKey as PublicKey, SystemPublicKey as SystemPublicKey,
93
+ Ed25519PublicKey as Ed25519PublicKey, Secp256k1PublicKey as Secp256k1PublicKey,
94
+ Signature as Signature, Ed25519Signature as Ed25519Signature,
95
+ Secp256k1Signature as Secp256k1Signature,
96
+ )
97
+
98
+ from .messages import MessagePayload as MessagePayload, MessageString as MessageString, MessageBytes as MessageBytes
99
+ from .caller import (
100
+ CallerInfo as CallerInfo, AccountCaller as AccountCaller, EntityCaller as EntityCaller,
101
+ ContractCaller as ContractCaller, CallStackElement as CallStackElement, Session as Session,
102
+ StoredSession as StoredSession, StoredContract as StoredContract,
103
+ )
104
+
105
+
106
+ _ExportFunction = TypeVar("_ExportFunction", bound=Callable[..., Any])
107
+
108
+
109
+ def export(function: _ExportFunction) -> _ExportFunction:
110
+ """Select a Wasm export; annotations apply only at its invocation boundary."""
111
+ return function
112
+
113
+ from .events import (
114
+ Event as Event, MessageEncoder as MessageEncoder, Bytesrepr as Bytesrepr,
115
+ Json as Json, JsonBytes as JsonBytes, emit as emit,
116
+ )
@@ -0,0 +1,104 @@
1
+ """Structural CL types shared by native Python and frozen contracts."""
2
+
3
+ from typing import Any
4
+ from .key import Key
5
+ from . import key as _key_types
6
+ from ._wire_types import _Unsigned, _Optional, _EnumRef, enum_type
7
+
8
+ NAMES = {0: "Bool", 3: "U8", 4: "U32", 5: "U64", 7: "U256", 8: "U512",
9
+ 9: "Unit", 10: "String", 11: "Key", 12: "URef", 13: "Option", 14: "List", 17: "Map",
10
+ 18: "Tuple1", 19: "Tuple2", 20: "Tuple3"}
11
+ BITS = {3: 8, 4: 32, 5: 64, 7: 256, 8: 512}
12
+
13
+
14
+ class CLType:
15
+ def __init__(self, tag: int, children: tuple[Any, ...] = (), annotation: Any = None) -> None:
16
+ if tag not in NAMES or len(children) != {13: 1, 14: 1, 17: 2, 18: 1, 19: 2, 20: 3}.get(tag, 0):
17
+ raise TypeError("Unsupported CL type descriptor")
18
+ self.tag, self.annotation = tag, annotation
19
+ self.depth = 0
20
+ resolved = []
21
+ for child in children:
22
+ child = child if isinstance(child, CLType) else resolve_type(child)
23
+ self.depth = max(self.depth, child.depth + 1)
24
+ if self.depth > 32:
25
+ raise TypeError("CL type nesting limit")
26
+ resolved.append(child)
27
+ self.children = tuple(resolved)
28
+
29
+ def __str__(self) -> str:
30
+ if self.annotation is bytes:
31
+ return "Bytes"
32
+ name = NAMES[self.tag]
33
+ if not self.children:
34
+ return name
35
+ parts = []
36
+ for child in self.children:
37
+ parts.append(str(child))
38
+ return name + "<" + ",".join(parts) + ">"
39
+
40
+ def __repr__(self) -> str:
41
+ return str(self)
42
+
43
+ def to_bytes(self) -> bytes:
44
+ data = bytes([self.tag])
45
+ for child in self.children:
46
+ data += child.to_bytes()
47
+ return data
48
+
49
+
50
+ SCALARS = {}
51
+ for _tag, _name in NAMES.items():
52
+ if _tag not in (13, 14, 17, 18, 19, 20):
53
+ SCALARS[_name] = CLType(_tag)
54
+ SCALARS["Key"].annotation = Key
55
+ SCALARS["Bytes"] = CLType(14, (SCALARS["U8"],), bytes)
56
+ # Key variants are SDK types known at startup. Reuse their descriptors rather
57
+ # than constructing objects underneath deeply nested contract imports.
58
+ KEY_TYPES = {Key: SCALARS["Key"]}
59
+ for _name in dir(_key_types):
60
+ _variant = getattr(_key_types, _name)
61
+ if isinstance(_variant, type) and _variant is not Key and issubclass(_variant, Key):
62
+ KEY_TYPES[_variant] = CLType(11, annotation=_variant)
63
+
64
+ # Compatibility for existing low-level callers. New type expressions never
65
+ # stringify their children and do not use this legacy-name boundary.
66
+ LEGACY = SCALARS.copy()
67
+ LEGACY["List<Key>"] = CLType(14, (SCALARS["Key"],))
68
+ LEGACY["Map<Key,U8>"] = CLType(17, (SCALARS["Key"], SCALARS["U8"]))
69
+
70
+
71
+ def resolve_type(annotation: Any, depth: int = 0) -> CLType:
72
+ if depth > 32:
73
+ raise TypeError("CL type nesting limit")
74
+ if isinstance(annotation, CLType):
75
+ return annotation
76
+ # A typed URef used as a wire value has Casper's URef CLType. Bare Key
77
+ # subclasses, including bare URef, retain their existing Key encoding.
78
+ from .references import URefType
79
+ if isinstance(annotation, URefType):
80
+ return CLType(12, annotation=annotation)
81
+ if isinstance(annotation, _Unsigned):
82
+ return SCALARS[annotation.kind]
83
+ if isinstance(annotation, _Optional):
84
+ return CLType(13, (resolve_type(annotation.annotation, depth + 1),))
85
+ if isinstance(annotation, _EnumRef) or enum_type(annotation) is not None:
86
+ return CLType(3, annotation=annotation)
87
+ if isinstance(annotation, type) and issubclass(annotation, Key):
88
+ if annotation not in KEY_TYPES:
89
+ KEY_TYPES[annotation] = CLType(11, annotation=annotation)
90
+ return KEY_TYPES[annotation]
91
+ if annotation is bytes:
92
+ return SCALARS["Bytes"]
93
+ if annotation is bool:
94
+ return SCALARS["Bool"]
95
+ if annotation is str:
96
+ return SCALARS["String"]
97
+ if annotation is None or annotation is type(None):
98
+ return SCALARS["Unit"]
99
+ if type(annotation) is str:
100
+ if annotation in LEGACY:
101
+ return LEGACY[annotation]
102
+ if annotation.startswith("Option<") and annotation.endswith(">"):
103
+ return CLType(13, (resolve_type(annotation[7:-1], depth + 1),))
104
+ raise TypeError("Use an explicit Casper wire type: " + str(annotation))