Skip to content

Commit

Permalink
Merge pull request #147 from Zondax/dev
Browse files Browse the repository at this point in the history
ERC20 transfer parser
  • Loading branch information
ftheirs committed Jan 8, 2024
2 parents 59ba927 + 9b07a53 commit 340c817
Show file tree
Hide file tree
Showing 56 changed files with 9,002 additions and 335 deletions.
6 changes: 5 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ project(ledger-filecoin VERSION 0.0.0)
enable_testing()

cmake_policy(SET CMP0025 NEW)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD 20)

option(ENABLE_FUZZING "Build with fuzzing instrumentation and build fuzz targets" OFF)
option(ENABLE_COVERAGE "Build with source code coverage instrumentation" OFF)
Expand Down Expand Up @@ -90,6 +90,7 @@ file(GLOB_RECURSE LIB_SRC
${CMAKE_CURRENT_SOURCE_DIR}/deps/ledger-zxlib/src/app_mode.c
${CMAKE_CURRENT_SOURCE_DIR}/deps/ledger-zxlib/src/bignum.c
${CMAKE_CURRENT_SOURCE_DIR}/deps/ledger-zxlib/src/zxmacros.c
${CMAKE_CURRENT_SOURCE_DIR}/deps/ledger-zxlib/src/zxformat.c
${CMAKE_CURRENT_SOURCE_DIR}/deps/BLAKE2/ref/blake2b-ref.c
####
${CMAKE_CURRENT_SOURCE_DIR}/app/src/parser.c
Expand All @@ -103,6 +104,9 @@ file(GLOB_RECURSE LIB_SRC
${CMAKE_CURRENT_SOURCE_DIR}/app/src/crypto_helper.c
${CMAKE_CURRENT_SOURCE_DIR}/app/src/crypto_test.c
${CMAKE_CURRENT_SOURCE_DIR}/app/src/base32.c
${CMAKE_CURRENT_SOURCE_DIR}/app/src/rlp.c
${CMAKE_CURRENT_SOURCE_DIR}/app/src/uint256.c
${CMAKE_CURRENT_SOURCE_DIR}/app/src/eth_erc20.c
)

add_library(app_lib STATIC
Expand Down
2 changes: 1 addition & 1 deletion app/Makefile.version
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ APPVERSION_M=0
# This is the minor version of this release
APPVERSION_N=23
# This is the patch version of this release
APPVERSION_P=11
APPVERSION_P=12
2 changes: 1 addition & 1 deletion app/src/common/parser_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ typedef enum {
parser_init_context_empty,
parser_display_idx_out_of_range,
parser_display_page_out_of_range,
parser_unexepected_error,
parser_unexpected_error,
// Cbor
parser_cbor_unexpected,
parser_cbor_unexpected_EOF,
Expand Down
126 changes: 126 additions & 0 deletions app/src/eth_erc20.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*******************************************************************************
* (c) 2018 - 2023 Zondax AG
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
********************************************************************************/

#include "eth_erc20.h"
#include "zxformat.h"

// Prefix is calculated as: keccak256("transfer(address,uint256)") = 0xa9059cbb
const uint8_t ERC20_TRANSFER_PREFIX[] = {0xa9, 0x05, 0x9c, 0xbb};
#define ERC20_DATA_LENGTH 68 // 4 + 32 + 32
#define ADDRESS_CONTRACT_LENGTH 20
#define DECIMAL_BASE 10
const erc20_tokens_t supportedTokens[] = {


{{0x3C, 0x35, 0x01, 0xE6, 0xC3, 0x53, 0xDb, 0xaE, 0xDD, 0xFA, 0x90, 0x37, 0x69, 0x75, 0xCe, 0x7a, 0xCe, 0x4A, 0xc7, 0xa8},
"stFIL ",
18},

{{0x60, 0xE1, 0x77, 0x36, 0x36, 0xCF, 0x5E, 0x4A, 0x22, 0x7d, 0x9A, 0xC2, 0x4F, 0x20, 0xfE, 0xca, 0x03, 0x4e, 0xe2, 0x5A},
"WFIL ",
18},

{{0x69, 0x09, 0x08, 0xf7, 0xfa, 0x93, 0xaf, 0xC0, 0x40, 0xCF, 0xbD, 0x9f, 0xE1, 0xdD, 0xd2, 0xC2, 0x66, 0x8A, 0xa0, 0xe0},
"iFIL ",
18},

{{0x6A, 0x3F, 0x21, 0xd2, 0xA9, 0x2a, 0x15, 0x75, 0x29, 0x12, 0x97, 0x4B, 0xbB, 0xD5, 0xb1, 0x46, 0x9A, 0x72, 0xB2, 0x61},
"wstFIL ",
18},

{{0xd0, 0x43, 0x77, 0x65, 0xD1, 0xDc, 0x0e, 0x2f, 0xA1, 0x4E, 0x97, 0xd2, 0x90, 0xF1, 0x35, 0xeF, 0xdF, 0x1a, 0x8a, 0x9A},
"clFIL ",
18},

{{0x42, 0x28, 0x49, 0xB3, 0x55, 0x03, 0x9b, 0xC5, 0x8F, 0x27, 0x80, 0xcc, 0x48, 0x54, 0x91, 0x9f, 0xC9, 0xcf, 0xaF, 0x94},
"USDT ",
6},
};

parser_error_t getERC20Token(const rlp_t *data, char tokenSymbol[MAX_SYMBOL_LEN], uint8_t *decimals) {
if (data == NULL || tokenSymbol == NULL || decimals == NULL ||
data->rlpLen != ERC20_DATA_LENGTH || memcmp(data->ptr, ERC20_TRANSFER_PREFIX, 4) != 0) {
return parser_unexpected_value;
}

// Verify address contract: first 12 bytes must be 0
const uint8_t *addressPtr = data->ptr + 4;
for (uint8_t i = 0; i < 12; i++) {
if (*(addressPtr++) != 0 ) {
return parser_unexpected_value;
}
}

// Check if token is in the list
const uint8_t supportedTokensSize = sizeof(supportedTokens)/sizeof(supportedTokens[0]);
for (uint8_t i = 0; i < supportedTokensSize; i++) {
if (memcmp(addressPtr, supportedTokens[i].address, ADDRESS_CONTRACT_LENGTH) == 0) {
// Set symbol and decimals
snprintf(tokenSymbol, 10, "%s", (char*) PIC(supportedTokens[i].symbol));
*decimals = supportedTokens[i].decimals;
return parser_ok;
}
}

// Unknonw token
snprintf(tokenSymbol, 10, "?? ");
*decimals = 0;
return parser_ok;
}
parser_error_t printERC20Value(const rlp_t *data, char *outVal, uint16_t outValLen,
uint8_t pageIdx, uint8_t *pageCount) {
if (data == NULL || outVal == NULL || pageCount == NULL) {
return parser_unexpected_error;
}

// [identifier (4) | token contract (12 + 20) | value (32)]
char tokenSymbol[10] = {0};
uint8_t decimals = 0;
CHECK_PARSER_ERR(getERC20Token(data, tokenSymbol, &decimals))

uint256_t value = {0};
const uint8_t *valuePtr = data->ptr + 4 + 12 + ADDRESS_CONTRACT_LENGTH;
parser_context_t tmpCtx = {.buffer = valuePtr, .bufferLen = 32, .offset = 0, .tx_type = eth_tx};
CHECK_PARSER_ERR(readu256BE(&tmpCtx, &value));

char bufferUI[100] = {0};
if (!tostring256(&value, DECIMAL_BASE, bufferUI, sizeof(bufferUI))) {
return parser_unexpected_error;
}

//Add symbol, add decimals, page number
if (intstr_to_fpstr_inplace(bufferUI, sizeof(bufferUI), decimals) == 0) {
return parser_unexpected_value;
}

if (z_str3join(bufferUI, sizeof(bufferUI), tokenSymbol, NULL) != zxerr_ok) {
return parser_unexpected_buffer_end;
}

number_inplace_trimming(bufferUI, 1);
pageString(outVal, outValLen, bufferUI, pageIdx, pageCount);

return parser_ok;
}

bool validateERC20(rlp_t data) {
// Check that data start with ERC20 prefix
if (data.rlpLen != ERC20_DATA_LENGTH || memcmp(data.ptr, ERC20_TRANSFER_PREFIX, 4) != 0) {
return false;
}

return true;
}
42 changes: 42 additions & 0 deletions app/src/eth_erc20.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*******************************************************************************
* (c) 2018 - 2023 Zondax AG
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
********************************************************************************/
#pragma once

#include <stdint.h>
#include "parser_common.h"
#include "rlp.h"
#include "coin.h"


#ifdef __cplusplus
extern "C" {
#endif

#define MAX_SYMBOL_LEN 10
typedef struct {
uint8_t address[ETH_ADDR_LEN];
char symbol[MAX_SYMBOL_LEN];
uint8_t decimals;
} erc20_tokens_t;

bool validateERC20(rlp_t data);
parser_error_t getERC20Token(const rlp_t *data, char tokenSymbol[MAX_SYMBOL_LEN], uint8_t *decimals);
parser_error_t printERC20Value(const rlp_t *data, char *outVal, uint16_t outValLen,
uint8_t pageIdx, uint8_t *pageCount);

#ifdef __cplusplus
}
#endif
103 changes: 33 additions & 70 deletions app/src/eth_utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
#include "eth_utils.h"
#include <stdio.h>
#include <zxmacros.h>
#include "zxerror.h"
#include "rlp.h"
#include "zxformat.h"
#include "coin.h"

#define CHECK_RLP_LEN(BUFF_LEN, RLP_LEN) \
{ \
Expand Down Expand Up @@ -48,11 +52,9 @@ saturating_add_u32(uint32_t a, uint32_t b)
return num;
}

int
be_bytes_to_u64(const uint8_t *bytes, uint8_t len, uint64_t *num)
{
parser_error_t be_bytes_to_u64(const uint8_t *bytes, uint8_t len, uint64_t *num) {
if (bytes == NULL || num == NULL || len == 0 || len > sizeof(uint64_t)) {
return -1;
return parser_unexpected_error;
}

*num = 0;
Expand All @@ -69,7 +71,7 @@ be_bytes_to_u64(const uint8_t *bytes, uint8_t len, uint64_t *num)
num_ptr++;
}

return 0;
return parser_ok;
}

rlp_error_t
Expand Down Expand Up @@ -132,76 +134,37 @@ get_tx_rlp_len(const uint8_t *buffer, uint32_t len, uint64_t *read, uint64_t *to
return rlp_invalid_data;
}

rlp_error_t
parse_rlp_item(const uint8_t *data,
uint32_t dataLen,
uint32_t *read,
uint32_t *item_len)
{
*item_len = 0;
*read = 0;

if (dataLen == 0)
return rlp_no_data;

uint8_t marker = data[0];

if (marker <= 0x7F) {
// first case item is just one byte
*read = 0;
*item_len = 1;
return rlp_ok;

} else if (marker <= 0xB7) {
// second case it is a string with a fixed length
uint8_t len = marker - 0x80;
*read = 1;
CHECK_RLP_LEN(dataLen, len + 1)
*item_len = len;
return rlp_ok;

} else if (marker <= 0xBF) {
// For strings longer than 55 bytes the length is encoded
// differently.
// The number of bytes that compose the length is encoded
// in the marker
// And then the length is just the number BE encoded
uint8_t num_bytes = marker - 0xB7;
uint64_t len = 0;
if (be_bytes_to_u64(&data[1], num_bytes, &len) != 0)
return rlp_invalid_data;

CHECK_RLP_LEN(dataLen, len + 1 + num_bytes)
*read = 1 + num_bytes;
*item_len = len;

return rlp_ok;
parser_error_t printRLPNumber(const rlp_t *num, char* outVal, uint16_t outValLen,
uint8_t pageIdx, uint8_t *pageCount) {
if (num == NULL || outVal == NULL || pageCount == NULL) {
return parser_unexpected_error;
}

} else if (marker <= 0xF7) {
// simple list
uint8_t len = marker - 0xC0;
uint256_t tmpUint256 = {0};
char tmpBuffer[100] = {0};

*read = 1;
CHECK_RLP_LEN(dataLen, len + 1)
*item_len = len;
return rlp_ok;
CHECK_PARSER_ERR(rlp_readUInt256(num, &tmpUint256));
if (!tostring256(&tmpUint256, 10, tmpBuffer, sizeof(tmpBuffer))) {
return parser_unexpected_error;
}
pageString(outVal, outValLen, tmpBuffer, pageIdx, pageCount);

// marker >= 0xF8
// For lists longer than 55 bytes the length is encoded
// differently.
// The number of bytes that compose the length is encoded
// in the marker
// And then the length is just the number BE encoded
uint8_t num_bytes = marker - 0xF7;
uint64_t len = 0;
if (be_bytes_to_u64(&data[1], num_bytes, &len) != 0)
return rlp_invalid_data;
return parser_ok;
}

CHECK_RLP_LEN(dataLen, len + 1 + num_bytes)
parser_error_t printEVMAddress(const rlp_t *address, char* outVal, uint16_t outValLen,
uint8_t pageIdx, uint8_t *pageCount) {
if (address == NULL || outVal == NULL || pageCount == NULL || address->rlpLen != ETH_ADDR_LEN) {
return parser_unexpected_error;
}

*read = 1 + num_bytes;
*item_len = len;
char tmpBuffer[67] = {0};
tmpBuffer[0] = '0';
tmpBuffer[1] = 'x';
if (!array_to_hexstr(tmpBuffer + 2, sizeof(tmpBuffer) - 2, address->ptr, address->rlpLen)) {
return parser_unexpected_error;
}
pageString(outVal, outValLen, tmpBuffer, pageIdx, pageCount);

return rlp_ok;
return parser_ok;
}
10 changes: 8 additions & 2 deletions app/src/eth_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

#include <stdio.h>
#include <zxmacros.h>
#include "parser_common.h"
#include "rlp.h"

#ifdef __cplusplus
extern "C" {
Expand All @@ -30,7 +32,6 @@ typedef enum RlpError {
} rlp_error_t;



// Add two numbers returning UINT64_MAX if overflows
uint64_t saturating_add(uint64_t a, uint64_t b);

Expand All @@ -51,8 +52,13 @@ rlp_error_t parse_rlp_item(const uint8_t *data, uint32_t dataLen, uint32_t *read

// converts a big endian stream of bytes to an u64 number.
// returns 0 on success, a negative number otherwise
int be_bytes_to_u64(const uint8_t *bytes, uint8_t len, uint64_t *num);
parser_error_t be_bytes_to_u64(const uint8_t *bytes, uint8_t len, uint64_t *num);

parser_error_t printRLPNumber(const rlp_t *num, char* outVal, uint16_t outValLen,
uint8_t pageIdx, uint8_t *pageCount);

parser_error_t printEVMAddress(const rlp_t *address, char* outVal, uint16_t outValLen,
uint8_t pageIdx, uint8_t *pageCount);
#ifdef __cplusplus
}
#endif
2 changes: 1 addition & 1 deletion app/src/fil_utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ parser_error_t printEthAddress(const address_t *a, char *outVal,
const uint16_t actorIdSize = decompressLEB128(a->buffer + 1, a->len - 1, &actorId);
const uint16_t payloadSize = a->len - 1 - actorIdSize;
if (payloadSize != 20) {
return parser_unexepected_error;
return parser_unexpected_error;
}

char outputBuffer[45] = {0};
Expand Down
Loading

0 comments on commit 340c817

Please sign in to comment.