Skip to content

Commit

Permalink
feat: add libevm
Browse files Browse the repository at this point in the history
  • Loading branch information
mpolitzer committed Oct 3, 2023
1 parent e62522c commit b12bc09
Show file tree
Hide file tree
Showing 12 changed files with 3,481 additions and 0 deletions.
2 changes: 2 additions & 0 deletions linux/libevm/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
doc/html
doc/theme
6 changes: 6 additions & 0 deletions linux/libevm/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
doc: doc/theme
doxygen doc/Doxyfile
doc/theme:
git clone --depth=1 --branch=v2.2.1 \
[email protected]:jothepro/doxygen-awesome-css.git $@
.PHONY: doc
2 changes: 2 additions & 0 deletions linux/libevm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# libevm

262 changes: 262 additions & 0 deletions linux/libevm/abi.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
/** @file
* @defgroup libevm_abi abi
*
* This is a C library to encode and decode Ethereum Virtual Machine (EVM)
* calldata. This format is used to interacts with contracts in the Ethereum
* ecosystem.
*
* We will cover the basic concepts required to use the API we provide, but for
* a complete reference consult the solidity specification, it can be found
* here: https://docs.soliditylang.org/en/latest/abi-spec.html
*
* ## Function Selector {#funsel}
*
* 4 bytes that identify the function name and parameter types. This is used to
* distinguish between different formats. To compute it, take the four first
* bytes of the `keccak` digest of the solidity function declaration. It should
* respect a canonical format and such as having no the variable names, check
* docs for details. For reference, for a hypotetical "FunctionName" function,
* it should look something like this:
* `keccak("FunctionName(type1,type2,...,typeN)");`
*
* ## Data Sections {#sections}
*
* After the function selector, we can start encoding the function parameters.
* One by one, left to right in two sections. `Static` for types with fixed
* size. Think ints, bools, addresses, etc. And then there is the `Dynamic`
* section. In this case, the encoded size depend on the data they contain, and
* how much of it, such as of strings and bytes. So:
*
* `Static` for ints, bools, adresses.
*
* `Dynamic` for bytes.
*
* ### Static Section {#static-section}
*
* Static types are encoded directly in the buffer when the API call is made.
* Put the data in and be done with it.
*
* ### Dynamic Section {#dynamic-section}
*
* Dynamic types require two calls. The first goes into the Static section to
* reserve space for a data offset. @ref evm_abi_put_bytes_s.
*
* After we are done with with all data that goes into the static section. We
* start to actually encode the dynamic data. And here is where the second call
* comes in. We patch the offset we reserved earlier with the actual data
* position and copy its contents over. @ref evm_abi_put_bytes_d.
*
* ## Encoder
*
* Lets look at some code:
*
* @includelineno "examples/abi_put.c"
*
* ## Decoder
*
* Lets look at some code:
*
* @includelineno "examples/abi_get.c"
*
* ## Complete
*
* Lets look at some code:
*
* @includelineno "examples/abi.c"
*
* @ingroup libevm
* @{ */
#ifndef EVM_ABI_H
#define EVM_ABI_H
#include"buf.h"

/** length of a evm address in bytes */
#define EVM_ADDRESS_LEN 20

/** Encode 4 bytes as a function selector */
#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
#define EVM_ABI_FUNSEL(A, B, C, D) \
( ((A) << 000) \
| ((B) << 010) \
| ((C) << 020) \
| ((D) << 030))
#else
#define EVM_ABI_FUNSEL(A, B, C, D) \
( ((D) << 000) \
| ((C) << 010) \
| ((B) << 020) \
| ((A) << 030))
#endif

// put section ---------------------------------------------------------------
/** Encode a function selector into the buffer @p me
*
* @param [in,out] me a initialized buffer working as iterator
* @param [in] funsel function selector
*
* @return
* - 0 success
* - ENOBUFS no space left in @p me
*
* @note A function selector can be compute it with: @ref evm_keccak_funsel.
* It is always represented in big endian. */
int
evm_abi_put_funsel(evm_buf_t *me, uint32_t funsel);

/** Encode a unsigned integer of up to 32bytes of data into the buffer
*
* @param [in,out] me a initialized buffer working as iterator
* @param [in] n size of @p data in bytes
* @param [in] data poiter to a integer
*
* @return
* - 0 success
* - ENOBUFS no space left in @p me
* - EDOM requested @p n is too large
*
* @code
* ...
* evm_buf_t it = ...;
* uint64_t x = UINT64_C(0xdeadbeef);
* evm_abi_put_uint(&it, sizeof x, &x);
* ...
* @endcode
* @note This function takes care of endianess conversions */
int
evm_abi_put_uint(evm_buf_t *me, size_t n, const void *data);

/** Encode @p address (exacly @ref EVM_ADDRESS_LEN bytes) into the buffer
*
* @param [in,out] me initialized buffer
* @param [in] address exactly @ref EVM_ADDRESS_LEN bytes
*
* @return
* - 0 success
* - ENOBUFS no space left in @p me */
int
evm_abi_put_address(evm_buf_t *me, const uint8_t address[EVM_ADDRESS_LEN]);

/** Encode the static part of @b bytes into the message,
* used in conjunction with @ref evm_abi_put_bytes_d
*
* @param [in,out] me initialized buffer
* @param [out] offset initialize for @ref evm_abi_put_bytes_d
* @return
* - 0 success
* - ENOBUFS no space left in @p me */
int
evm_abi_put_bytes_s(evm_buf_t *me, evm_buf_t *offset);

/** Encode the dynamic part of @b bytes into the message,
* used in conjunction with @ref evm_abi_put_bytes_d
*
* @param [in,out] me initialized buffer
* @param [in] offset initialized from @ref evm_abi_put_bytes_h
* @param [in] n size of @b data
* @param [in] data array of bytes
* @return
* - 0 success
* - ENOBUFS no space left in @p me */
int
evm_abi_put_bytes_d(evm_buf_t *me, evm_buf_t *offset
,size_t n, const void *data);

/** Reserve @b n bytes of data from the buffer into @b res to be filled by the
* caller
*
* @param [in,out] me initialized buffer
* @param [in] n amount of bytes to reserve
* @param [out] res slice of bytes extracted from @p me
* @return
* - 0 success
* - ENOBUFS no space left in @p me
*
* @note @p me must outlive @p res.
* Create a duplicate otherwise */
int
evm_abi_res_bytes(evm_buf_t *me, size_t n, evm_buf_t *res);

// get section ---------------------------------------------------------------

/** Read the funsel without advancing @p me
*
* @param [in] me initialized buffer
* @return
* - The function selector
*
* @code
* ...
* if (evm_buf_length(it) < 4)
* return EXIT_FAILURE;
* switch (evm_abi_peek_funsel(it) {
* case EVM_ABI_FUNSEL(...): // known type, try to parse it
* case EVM_ABI_FUNSEL(...): // known type, try to parse it
* default:
* return EXIT_FAILURE;
* }
* @endcode
*
* @note user must ensure there are at least 4 bytes in the buffer.
* This function will fail and return 0 if that is not the case. */
uint32_t
evm_abi_peek_funsel(evm_buf_t *me);

/** Read the funsel from buffer @p me and match it against @p funsel
*
* @param [in,out] me initialized buffer
* @param [in] expected_funsel expected function selector
*
* @return
* - 0 success
* - ENOBUFS no space left in @p me
* - EBADMSG no case of a missmatch */
uint32_t
evm_abi_check_funsel(evm_buf_t *me, uint32_t expected_funsel);

/** Decode a unsigned integer of up to 32bytes from the buffer
*
* @param [in,out] me initialized buffer
* @param [in] n size in bytes of @p data
* @param [out] data pointer to a integer
* @return
* - 0 success
* - ENOBUFS no space left in @p me
* - EDOM value won't fit into @p n bytes. */
int
evm_abi_get_uint(evm_buf_t *me, size_t n, void *data);

/** Decode @b address (exacly 20 bytes) from the buffer
*
* @param [in,out] me initialized buffer
* @param [out] address exactly 20 bytes
*
* @return
* - 0 success
* - ENOBUFS requested size @b n is not available */
int
evm_abi_get_address(evm_buf_t *me, uint8_t address[EVM_ADDRESS_LEN]);

/** Decode the @p bytes offset
*
* @param [in,out] me initialized buffer
* @param [out] of offset of @p bytes, access contents with @ref evm_abi_get_bytes_d.
* @return
* - 0 success
* - ENOBUFS no space left in @p me */
int
evm_abi_get_bytes_s(evm_buf_t *me, evm_buf_t of[1]);

/** Decode @b bytes from the buffer by taking a pointer to its contents.
*
* @param [in,out] me initialized buffer
* @param [out] n amount of data available in @b bytes
* @param [out] bytes memory range with contents
* @return
* - 0 success
* - ENOBUFS no space left in @p me
* @note @p of can be initialized by calling @ref evm_abi_get_bytes_s */
int
evm_abi_get_bytes_d(evm_buf_t *me, evm_buf_t of[1], size_t *n, void **bytes);

#endif /* EVM_ABI_H */
/** @} */
69 changes: 69 additions & 0 deletions linux/libevm/buf.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/** @file
* @defgroup libevm_buf buf
* slice of contiguous memory
*
* @ingroup libevm
* @{ */
#ifndef EVM_BUF_H
#define EVM_BUF_H
#include<stdint.h>
#include<stddef.h>

/** A slice of contiguous memory from @b p to @b q.
* Size can be taken with: `q - p`. */
typedef struct {
uint8_t *p; /**< start of memory region */
uint8_t *q; /**< end of memory region */
} evm_buf_t;

/** Initialize a @b evm_buf_t */
#define EVM_BUF_INIT(S, N, XS) \
{ .p = (uint8_t *)(XS), \
.q = (uint8_t *)(XS) + (N), \
}

/** Declare a evm_buf_t with memory backed by the stack.
* @param [in] N - size in bytes */
#define EVM_BUF_DECL(S, N) evm_buf_t S = \
{ .p = (uint8_t [N]){0}, \
.q = (&S)->p + N, \
}

/** Initialize @p me buffer backed by @p data, @p n bytes in size
*
* @param [out] me a uninitialized instance
* @param [in] n size in bytes of @b data
* @param [in] data the backing memory to be used.
*
* @note @p data memory must outlive @p me.
* user must copy the contents otherwise */
void evm_buf_init(evm_buf_t *me, size_t n, void *data);

/** Split a buffer in two, @b lhs with @b n bytes and @b rhs with the rest
*
* @param [in,out] me initialized buffer
* @param [in] n bytes in @b lhs
* @param [out] lhs left hand side
* @param [out] rhs right hand side
*
* @return
* - 0 success
* - ENOBUFS requested size @b n is not available */
int evm_buf_split(const evm_buf_t *me, size_t n, evm_buf_t *lhs, evm_buf_t *rhs);

/** Length in bytes of @p me, can also signify the space left if used as iterator.
*
* @param [in] me initialized buffer
* @return
* - size in bytes */
size_t evm_buf_length(const evm_buf_t *me);

/** Print the contents of @b me buffer to stdout
*
* @param [in] p start of memory region
* @param [in] q end of memory region
* @param [in] l bytes per line (must be a power of 2). */
void evm_buf_xxd(void *p, void *q, int l);

#endif /* EVM_BUF_H */
/** @} */
Loading

0 comments on commit b12bc09

Please sign in to comment.