Skip to content

v2 draft

Marcelo Politzer Couto edited this page Jun 23, 2026 · 6 revisions

New APIs

#ifndef CMT_H
#define CMT_H
#include "libcmt/abi.h"
#include"rollup.h"

// notice variants -------------------------------------------------------------
int cmt_emit_notice(
	cmt_rollup_t *me,
	cmt_buf_t *data
);

int cmt_emit_notice1(
	cmt_rollup_t *me,
	cmt_abi_u256_t *arg1,
	cmt_buf_t *data
);

int cmt_emit_notice2(
	cmt_rollup_t *me,
	cmt_abi_u256_t *arg1,
	cmt_abi_u256_t *arg2,
	cmt_buf_t *data
);

int cmt_emit_notice3(
	cmt_rollup_t *me,
	cmt_abi_u256_t *arg1,
	cmt_abi_u256_t *arg2,
	cmt_abi_u256_t *arg3,
	cmt_buf_t *data
);

// call voucher variants -------------------------------------------------------
int cmt_emit_call_voucher(
	cmt_rollup_t *me,
	cmt_abi_address_t *destination,
	cmt_abi_u256_t *value,
	cmt_buf_t *data
);

int cmt_emit_call_voucher1(
	cmt_rollup_t *me,
	cmt_abi_address_t *destination,
	cmt_abi_u256_t *value,
	cmt_abi_u256_t *arg1,
	cmt_buf_t *data
);

// delegate call voucher variants ----------------------------------------------
int cmt_emit_delegate_call_voucher(
	cmt_rollup_t *me,
	cmt_abi_address_t *destination,
	cmt_buf_t *data
);

int cmt_emit_delegate_call_voucher1(
	cmt_rollup_t *me,
	cmt_abi_address_t *destination,
	cmt_abi_u256_t *arg1,
	cmt_buf_t *data
);

int cmt_emit_delegate_call_voucher2(
	cmt_rollup_t *me,
	cmt_abi_address_t *destination,
	cmt_abi_u256_t *arg1,
	cmt_abi_u256_t *arg2,
	cmt_buf_t *data
);

// ether transfers variants ----------------------------------------------------
int cmt_emit_ether_transfer(
	cmt_rollup_t *me,
	cmt_abi_address_t *recipient,
	cmt_abi_u256_t *value,
	cmt_buf_t *data
);

int cmt_emit_ether_transfer1(
	cmt_rollup_t *me,
	cmt_abi_address_t *recipient,
	cmt_abi_u256_t *value,
	cmt_abi_u256_t *arg1,
	cmt_buf_t *data
);

// erc-20 transfers variants ---------------------------------------------------
int cmt_emit_erc20_transfer(
	cmt_rollup_t *me,
	cmt_abi_address_t *recipient,
	cmt_abi_u256_t *token,
	cmt_abi_u256_t *value,
	cmt_buf_t *data
);

// erc-721 transfers variants --------------------------------------------------
int cmt_emit_erc721_transfer(
	cmt_rollup_t *me,
	cmt_abi_address_t *recipient,
	cmt_abi_u256_t *token,
	cmt_abi_u256_t *token_id,
	cmt_buf_t *data
);

// erc-1155 single transfers ---------------------------------------------------
int cmt_emit_erc1155_single_transfer(
	cmt_rollup_t *me,
	cmt_abi_address_t *recipient,
	cmt_abi_u256_t *token,
	cmt_abi_u256_t *token_id,
	cmt_buf_t *data
);

// erc-1155 batch transfers ----------------------------------------------------
int cmt_emit_erc1155_batch_transfer(
	cmt_rollup_t *me,
	cmt_abi_address_t *recipient,
	cmt_abi_u256_t *token,
	cmt_abi_u256_t *token_id,
	cmt_buf_t *data
);

#endif /* CMT_H */

Ideas for the future

// htif.h ----------------------------------------------------------------------
// For internal usage, lib exports only ADVANCE & INSPECT?
enum {
	HTIF_YIELD_CMD_AUTOMATIC = 0,
	HTIF_YIELD_CMD_MANUAL = 1,
};

enum {
	HTIF_YIELD_AUTOMATIC_REASON_PROGRESS = 1,
	HTIF_YIELD_AUTOMATIC_REASON_TX_OUTPUT = 2,
	HTIF_YIELD_AUTOMATIC_REASON_TX_REPORT = 4,
};

enum {
	HTIF_YIELD_MANUAL_REASON_RX_ACCEPTED = 1,
	HTIF_YIELD_MANUAL_REASON_RX_REJECTED = 2,
	HTIF_YIELD_MANUAL_REASON_TX_EXCEPTION = 4,
};

enum {
	HTIF_YIELD_REASON_ADVANCE = 0,
	HTIF_YIELD_REASON_INSPECT = 1,
};

// buf.h -----------------------------------------------------------------------
// mostly unchanged. begin & end types changed to better handle UB on pointer
// arithmetic based bounds checking.
#include<stdint.h>
#include<stddef.h>

typedef struct cmt_buf {
	uintptr_t begin;
	uintptr_t end;
} cmt_buf_t;

cmt_buf_t cmt_buf_init (size_t length, uint8_t data[length]);
void     *cmt_buf_view (cmt_buf_t *, size_t *length);
int       cmt_buf_split(cmt_buf_t *, size_t length, cmt_buf_t *lhs, cmt_buf_t *rhs);

// io.h ------------------------------------------------------------------------
// mostly unchanged. cmt_io_t contents got type erased to better support the
// multiple backends. This simplify the symbols exported by the lib. Each
// implementation checks its own type against the reserved space with a
// static_assert.
#include<stddef.h>
#include<stdint.h>

typedef struct {
	uint64_t data[26];
} cmt_io_t;

typedef struct {
	uint8_t dev;
	uint8_t cmd;
	uint16_t reason;
	uint32_t data;
} cmt_io_yield_t;

int       cmt_io_init  (cmt_io_t *);
void      cmt_io_fini  (cmt_io_t *);
cmt_buf_t cmt_io_get_tx(cmt_io_t *);
cmt_buf_t cmt_io_get_rx(cmt_io_t *);
int       cmt_io_yield (cmt_io_t *, cmt_io_yield_t *rr);

// io.c ------------------------------------------------------------------------
// renamed from cmt_io_driver_ioctl_t to cmt_io_linux_t.
typedef struct {
	cmt_buf_t tx[1];
	cmt_buf_t rx[1];
	int fd;
} cmt_io_linux_t;
static_assert(sizeof(cmt_io_linux_t) <= sizeof(cmt_io_t));

// io-mock.c -------------------------------------------------------------------
// renamed from cmt_io_driver_mock_t to cmt_io_mock_t.
typedef struct {
	cmt_buf_t tx[1];
	cmt_buf_t rx[1];
	cmt_buf_t inputs_left;

	int  input_type;
	char input_filename[128];
	char input_fileext[8];

	uint32_t input_seq;
	uint32_t output_seq;
	uint32_t report_seq;
	uint32_t exception_seq;
} cmt_io_mock_t;
static_assert(sizeof(cmt_io_mock_t) <= sizeof(cmt_io_t));

// io-guest.c ------------------------------------------------------------------
// host mode (guest side) uses the same APIs as linux and mock versions.
// cmt_io_init will open a UNIX socket created by the host; retrive two file
// descriptors for tx and rx.
typedef struct {
	cmt_buf_t tx[1];
	cmt_buf_t rx[1];
	int fd;
} cmt_io_guest_t;
static_assert(sizeof(cmt_io_guest_t) <= sizeof(cmt_io_t));

// io-host.c -------------------------------------------------------------------
// host mode (host side) has a API that mimics the machine-emulator.
// cmt_io_host_init creates a UNIX socket and two temporary files, one for tx,
// one for rx, buffers. It then waits for a connection from guest before proceding.
typedef struct {
	cmt_buf_t tx[1];
	cmt_buf_t rx[1];
	int fd;
} cmt_io_host_t;
static_assert(sizeof(cmt_io_host_t) <= sizeof(cmt_io_t));

int cmt_io_host_init                (cmt_io_host_t *);
int cmt_io_host_fini                (cmt_io_host_t *);

int cmt_io_host_send_cmio_response  (cmt_io_host_t *, uint16_t reason, const uint8_t *data, uint64_t length);
int cmt_io_host_run                 (cmt_io_host_t *, uint16_t *break_reason);
int cmt_io_host_receive_cmio_request(cmt_io_host_t *, uint8_t *cmd, uint16_t *reason, uint8_t *data, uint64_t *length);

// keccak.h --------------------------------------------------------------------
// unchanged
enum { CMT_KECCAK_LENGTH = 32 };

typedef union {
    uint8_t b[200];
    uint64_t q[25];
} cmt_keccak_state_t;

typedef struct cmt_keccak {
    cmt_keccak_state_t st;
    int pt, rsiz;
} cmt_keccak_t;

void     cmt_keccak_init  (cmt_keccak_t *);
void     cmt_keccak_update(cmt_keccak_t *, size_t n, const void *data);
void     cmt_keccak_final (cmt_keccak_t *, void *md);
uint8_t *cmt_keccak_data  (size_t length, const void *data, uint8_t md[CMT_KECCAK_LENGTH]);

uint32_t cmt_keccak_funsel(const char *decl);

// merkle.h --------------------------------------------------------------------
// unchanged
enum { CMT_MERKLE_TREE_HEIGHT = 63 };

typedef struct {
	uint64_t leaf_count;
	uint8_t state[CMT_MERKLE_TREE_HEIGHT][CMT_KECCAK_LENGTH];
} cmt_merkle_t;

void     cmt_merkle_init          (cmt_merkle_t *);
void     cmt_merkle_fini          (cmt_merkle_t *);

int      cmt_merkle_load          (cmt_merkle_t *, const char *filepath);
int      cmt_merkle_save          (cmt_merkle_t *, const char *filepath);
uint64_t cmt_merkle_get_leaf_count(cmt_merkle_t *);

int      cmt_merkle_push_back     (cmt_merkle_t *, const uint8_t hash[CMT_KECCAK_LENGTH]);
int      cmt_merkle_push_back_data(cmt_merkle_t *, size_t length, const void *data);
void     cmt_merkle_get_root_hash (cmt_merkle_t *, uint8_t root[CMT_KECCAK_LENGTH]);

// rollup.h --------------------------------------------------------------------
// rollup got simplified. cmt_rollup_get_merkle and cmt_rollup_get_io got added
// as escape hatches for advanced use cases. ABI encoded structs got removed and
// will be handled by a separate layer above.
//
// Emit output and report also have a zero copy mode when `data` is NULL. In this
// mode, neither function touches the tx buffer prior to notifying the host of
// the data. This allows users to safely encode data directly to the tx buffer.
typedef struct {
	cmt_io_t     io[1];
	cmt_merkle_t merkle[1];
} cmt_rollup_t;

enum {
	CMT_REQ_TYPE_ADVANCE = 0,
	CMT_REQ_TYPE_INSPECT = 1,
};

int           cmt_rollup_init            (cmt_rollup_t *, cmt_buf_t *tx);
int           cmt_rollup_fini            (cmt_rollup_t *);

cmt_merkle_t *cmt_rollup_get_merkle      (cmt_rollup_t *);
cmt_io_t     *cmt_rollup_get_io          (cmt_rollup_t *);

int           cmt_rollup_wait_for_input  (cmt_rollup_t *, cmt_buf_t *rx);
int           cmt_rollup_reject          (cmt_rollup_t *);

int           cmt_rollup_emit_output     (cmt_rollup_t *, size_t length, const void *data);
int           cmt_rollup_emit_report     (cmt_rollup_t *, size_t length, const void *data);

// abi.h -----------------------------------------------------------------------
// abi got new types: an ABI writter and ABI reader. Their main purpose is to
// simplify buffer size calculations. They also make the API a bit more discoverable.
// `bytes_s` and `bytes_d` got reworked and renamed to something more descriptive.
// NOTE: this may be removed in favor of a separate lib, TBD.
typedef struct {
	// ...
	int stub;
} cmt_wr_t;

typedef struct {
	// ...
	int stub;
} cmt_rd_t;

typedef struct {
	// ...
	int stub;
} cmt_frame_t;

typedef struct {
	uint8_t data[32];
} cmt_256_t;

typedef struct {
	uint8_t data[20];
} cmt_address_t;

typedef struct {
	// ...
	int stub;
} cmt_wr_bytes_state_t;

int    cmt_wr_init          (cmt_wr_t *, cmt_buf_t *tx);
size_t cmt_wr_get_used      (cmt_wr_t *);
size_t cmt_wr_get_available (cmt_wr_t *);

int    cmt_wr_funsel        (cmt_wr_t *, uint32_t funsel);
int    cmt_wr_256           (cmt_wr_t *, cmt_256_t *data);
int    cmt_wr_address       (cmt_wr_t *, cmt_address_t *data);
int    cmt_wr_frame         (cmt_wr_t *, cmt_frame_t *frame);
int    cmt_wr_bytes_meta    (cmt_wr_t *, cmt_wr_bytes_state_t *, const cmt_frame_t *frame);
int    cmt_wr_bytes_data    (cmt_wr_t *, cmt_wr_bytes_state_t *, cmt_buf_t *data);
int    cmt_wr_bytes_reserve (cmt_wr_t *, cmt_wr_bytes_state_t *wr_bytes, cmt_buf_t *available);
int    cmt_wr_bytes_commit  (cmt_wr_t *, cmt_wr_bytes_state_t *wr_bytes, size_t used);


int    cmt_rd_init              (cmt_rd_t *, cmt_buf_t *rx);

int    cmt_rd_funsel_check  (cmt_rd_t *, uint32_t funsel);
int    cmt_rd_funsel        (cmt_rd_t *, uint32_t *funsel);
int    cmt_rd_uint256       (cmt_rd_t *, cmt_256_t *data);
int    cmt_rd_address       (cmt_rd_t *, cmt_address_t *data);
int    cmt_rd_frame         (cmt_rd_t *, cmt_frame_t *frame);
int    cmt_rd_bytes_meta    (cmt_rd_t *, cmt_wr_bytes_state_t *, const cmt_frame_t *frame);
int    cmt_rd_bytes_view    (cmt_rd_t *, cmt_wr_bytes_state_t *, cmt_buf_t *view);

// codec.h ?--------------------------------------------------------------------
// This needs a better name!
int cmt_emit_outputN(cmt_rollup_t *,
	size_t nargs,
	const cmt_256_t *arg[nargs],
	const cmt_buf_t *bytes);

int cmt_emit_output0(cmt_rollup_t *,
	const cmt_buf_t *bytes);

int cmt_emit_output1(cmt_rollup_t *,
	const cmt_256_t *arg0,
	const cmt_buf_t *bytes);

int cmt_emit_output2(cmt_rollup_t *,
	const cmt_256_t *arg0,
	const cmt_256_t *arg1,
	const cmt_buf_t *bytes);

int cmt_emit_output3(cmt_rollup_t *,
	const cmt_256_t *arg0,
	const cmt_256_t *arg1,
	const cmt_256_t *arg2,
	const cmt_buf_t *bytes);

int cmt_emit_output4(cmt_rollup_t *,
	const cmt_256_t *arg0,
	const cmt_256_t *arg1,
	const cmt_256_t *arg2,
	const cmt_256_t *arg3,
	const cmt_buf_t *bytes);
// memory layouts
typedef struct {
	uint8_t funsel[4];
	uint8_t arg0[32];
	uint8_t offset[32];
	uint8_t length[32];
	uint8_t data[];
} cmt_output1;

typedef struct {
	uint8_t funsel[4];
	uint8_t arg0[32];
	uint8_t arg1[32];
	uint8_t offset[32];
	uint8_t length[32];
	uint8_t data[];
} cmt_output2;

typedef struct {
	uint8_t funsel[4];
	uint8_t arg0[32];
	uint8_t arg1[32];
	uint8_t arg2[32];
	uint8_t offset[32];
	uint8_t length[32];
	uint8_t data[];
} cmt_output3;

typedef struct {
	uint8_t funsel[4];
	uint8_t arg0[32];
	uint8_t arg1[32];
	uint8_t arg2[32];
	uint8_t arg3[32];
	uint8_t offset[32];
	uint8_t length[32];
	uint8_t data[];
} cmt_output4;

typedef struct {
	uint8_t funsel[4];
	uint8_t chain_id[32];
	uint8_t app_contract[32]; // read as [20]
	uint8_t msg_sender[32];   // read as [20]
	uint8_t block_number[32];
	uint8_t block_timestamp[32];
	uint8_t prev_randao[32];
	uint8_t index[32];
	uint8_t offset[32];
	uint8_t length[32];
	uint8_t data[];
} cmt_advance_t;
// advance decode could look something like this using: https://github.com/mhw0/libethc
typedef struct {
	uint64_t chain_id;
	char app_contract[2*CMT_ADDRESS_LENGTH+1];
	char msg_sender[2*CMT_ADDRESS_LENGTH+1];
	uint64_t block_number;
	uint64_t block_timestamp;
	mp_int prev_randao;
	uint64_t index;
	size_t payload_length;
	void *payload_data;
} advance_t;

void decode_advance(cmt_rollup_t *rollup, advance_t *advance) {
	struct eth_abi abi[1] = {0};
	if (eth_abi_init(abi, ETH_ABI_DECODE) != ETH_OK
		cmt_rollup_reject(rollup);
	if (eth_abi_uint64(abi, &advance->chain_id) != ETH_OK)
		cmt_rollup_reject(rollup);
	if (eth_abi_address(abi, &advance->app_contract) != ETH_OK)
		cmt_rollup_reject(rollup);
	if (eth_abi_address(abi, &advance->msg_sender) != ETH_OK)
		cmt_rollup_reject(rollup);
	if (eth_abi_uint64(abi, &advance->block_number) != ETH_OK)
		cmt_rollup_reject(rollup);
	if (eth_abi_uint64(abi, &advance->block_timestamp) != ETH_OK)
		cmt_rollup_reject(rollup);
	if (eth_abi_mpint(abi, &advance->prev_randao) != ETH_OK)
		cmt_rollup_reject(rollup);
	if (eth_abi_uint64(abi, &advance->index) != ETH_OK)
		cmt_rollup_reject(rollup);

	// how long?
	advance->payload_length = 2 << 20UL;
	advance->payload = malloc(max); // maximum of 2MB
	if (advance->payload == NULL)
		cmt_rollup_reject(rollup);

	if (eth_abi_bytes(abi, advance->payload, &advance->payload_length) != ETH_OK)
		cmt_rollup_reject(rollup);

	return abi;
}

// and the main loop like this
int main() {
	cmt_buf_t tx[1], rx[1];
	cmt_rollup_t R[1];

	if (cmt_rollup_init(R, tx) < 0)
		exit(1);

	for (int req_type; (req_type = cmt_rollup_wait_for_input(R, rx)) < 0; ) {
		switch (req_type) {
		case ADVANCE:
			decode_advance(R, advance);
			break;

		case INSPECT:
			break;
		}
	}
}

Clone this wiki locally