Skip to main content

Contracts are actually running

·3 mins·
status-update

After a lot of refactoring and preparation, native execution environment is finally functional and can be used for purposes like writing tests and debugging.

While the previous update mentioned metadata decoding for methods, turns out full metadata decoding was necessary for native execution environment and was implemented in PR 45 with some fixes and optimizations landing in PR 46 shortly afterward. One important change there was avoiding #[slot] and #[tmp] type repetition in metadata, instead it is stored in the main contract metadata right next to the state metadata, which is part of the reason why full metadata decoding was needed at this stage.

After that, PR 47 made one of the last changes to FFI interface, moving size and capacity as pointers next to the data pointer in both InternalArgs and ExternalArgs for easier processing by the host. It really made things a lot simpler to handle than before and since they are pointers now, there are no issues with alignment of fields that have different types in those data structures.

With that, PR 50 finally introduced a native execution environment alongside some basic tests of the example contract. PR 55 further simplified things a bit, and as of right now, a contract test looks something like this. As you can see, it supports deploying system contracts first, deploying user contracts, various calls into contracts both from outside execution environment and cross-contract calls, both directly and through trait interfaces like Fungible trait for pseudo-token (implemented by example contract). A massive step forward overall!

I mentioned InternalArgs and ExternalArgs before, but what are they? Great question indeed! With FFI interface being a bit more stable now, I have expanded contract macro documentation with details about what code it actually generates and how it is supposed to be used in PR 52. I know it is a lot of text, but it should still be a bit easier to comprehend than trying to infer what it does and why from the macros source code (even though I tried to document it as well).

In the process of doing that, I was more and more annoyed by the fact that there are raw pointers in Rust that can express whether the pointer can be used for writes (*mut T vs *const T) and there is a pointer that is guaranteed to be not null (NonNull), but there is no NonNullConst and NonNullMut pair or similar. Discussion on Rust Internals indicates I’m not alone, so hopefully things will improve in the future and make FFI interfaces even better.

Outside of code changes, there was one researcher interview and there are a few more leads that will hopefully turn into more interviews later, thanks Michelle! Two developer interviews are also planned for next week; I’ll share how those went hopefully in the next update.

Upcoming plans
#

While the native execution environment is there and even supports recursive calls into other contracts, it unfortunately doesn’t perform a couple of important checks. First it doesn’t reject #[update] or #[init] calls from #[view] methods, but it really should despite generated extension traits making it impossible to compile when using high-level APIs. Second, conflicting state modifications in recursive calls are not prohibited, which are trivial to do in contracts to the first issue. I’ll work on fixing these next.

And interviews I’m sure will lead to some interesting feedback to reflect on. If you know someone with smart contact or blockchain development experience that would be good to talk to, let me know.