Developing with C
Renec supports writing on-chain programs using the C and C++ programming languages.
#
Project LayoutC projects are laid out as follows:
The makefile
should contain the following:
The bpf-sdk may not be in the exact place specified above but if you setup your environment per How to Build then it should be.
Take a look at helloworld for an example of a C program.
#
How to BuildFirst setup the environment:
- Install the latest Rust stable from https://rustup.rs
- Install the latest Renec command-line tools from https://docs.renec.foundation/cli/install-renec-cli-tools
Then build using make:
#
How to TestRenec uses the Criterion test framework and tests are executed each time the program is built How to Build.
To add tests, create a new file next to your source file named test_<program name>.c
and populate it with criterion test cases. For an example see the
helloworld C
tests
or the Criterion docs for
information on how to write a test case.
#
Program EntrypointPrograms export a known entrypoint symbol which the Renec runtime looks up and calls when invoking a program. Renec supports multiple versions of the BPF loader and the entrypoints may vary between them. Programs must be written for and deployed to the same loader. For more details see the overview.
Currently there are two supported loaders BPF Loader and BPF loader deprecated
They both have the same raw entrypoint definition, the following is the raw symbol that the runtime looks up and calls:
This entrypoint takes a generic byte array which contains the serialized program parameters (program id, accounts, instruction data, etc...). To deserialize the parameters each loader contains its own helper function.
Refer to helloworld's use of the entrypoint as an example of how things fit together.
#
SerializationRefer to helloworld's use of the deserialization function.
Each loader provides a helper function that deserializes the program's input parameters into C types:
Some programs may want to perform deserialization themselves, and they can by providing their own implementation of the raw entrypoint. Take note that the provided deserialization functions retain references back to the serialized byte array for variables that the program is allowed to modify (lamports, account data). The reason for this is that upon return the loader will read those modifications so they may be committed. If a program implements their own deserialization function they need to ensure that any modifications the program wishes to commit must be written back into the input byte array.
Details on how the loader serializes the program inputs can be found in the Input Parameter Serialization docs.
#
Data TypesThe loader's deserialization helper function populates the SolParameters structure:
'ka' is an ordered array of the accounts referenced by the instruction and represented as a SolAccountInfo structures. An account's place in the array signifies its meaning, for example, when transferring lamports an instruction may define the first account as the source and the second as the destination.
The members of the SolAccountInfo
structure are read-only except for
lamports
and data
. Both may be modified by the program in accordance with
the runtime enforcement
policy. When an instruction
reference the same account multiple times there may be duplicate
SolAccountInfo
entries in the array but they both point back to the original
input byte array. A program should handle these cases delicately to avoid
overlapping read/writes to the same buffer. If a program implements their own
deserialization function care should be taken to handle duplicate accounts
appropriately.
data
is the general purpose byte array from the instruction's instruction
data being
processed.
program_id
is the public key of the currently executing program.
#
HeapC programs can allocate memory via the system call
calloc
or implement their own heap on top of the 32KB heap region starting at virtual
address x300000000. The heap region is also used by calloc
so if a program
implements their own heap it should not also call calloc
.
#
LoggingThe runtime provides two system calls that take data and log it to the program logs.
The debugging section has more information about working with program logs.
#
Compute BudgetUse the system call
sol_log_compute_units()
to log a message containing the remaining number of compute units the program
may consume before execution is halted
See compute budget for more information.
#
ELF DumpThe BPF shared object internals can be dumped to a text file to gain more insight into a program's composition and what it may be doing at runtime. The dump will contain both the ELF information as well as a list of all the symbols and the instructions that implement them. Some of the BPF loader's error log messages will reference specific instruction numbers where the error occurred. These references can be looked up in the ELF dump to identify the offending instruction and its context.
To create a dump file:
#
ExamplesThe Renec Program Library github repo contains a collection of C examples