diff --git a/src/eth_plugin_internal.c b/src/eth_plugin_internal.c index b266969..0e28ca6 100644 --- a/src/eth_plugin_internal.c +++ b/src/eth_plugin_internal.c @@ -4,6 +4,7 @@ bool erc20_plugin_available_check(void); void erc20_plugin_call(int message, void* parameters); +void compound_plugin_call(int message, void* parameters); void copy_address(uint8_t* dst, const uint8_t* parameter, uint8_t dst_size) { uint8_t copy_size = MIN(dst_size, ADDRESS_LENGTH); @@ -28,6 +29,17 @@ static const uint8_t ERC20_APPROVE_SELECTOR[SELECTOR_SIZE] = {0x09, 0x5e, 0xa7, const uint8_t* const ERC20_SELECTORS[NUM_ERC20_SELECTORS] = {ERC20_TRANSFER_SELECTOR, ERC20_APPROVE_SELECTOR}; +static const uint8_t COMPOUND_REDEEM_UNDERLYING_SELECTOR[SELECTOR_SIZE] = {0x85, 0x2a, 0x12, 0xe3}; +static const uint8_t COMPOUND_REDEEM_SELECTOR[SELECTOR_SIZE] = {0xdb, 0x00, 0x6a, 0x75}; +static const uint8_t COMPOUND_MINT_SELECTOR[SELECTOR_SIZE] = {0xa0, 0x71, 0x2d, 0x68}; +static const uint8_t CETH_MINT_SELECTOR[SELECTOR_SIZE] = {0x12, 0x49, 0xc5, 0x8b}; + +const uint8_t* const COMPOUND_SELECTORS[NUM_COMPOUND_SELECTORS] = { + COMPOUND_REDEEM_UNDERLYING_SELECTOR, + COMPOUND_REDEEM_SELECTOR, + COMPOUND_MINT_SELECTOR, + CETH_MINT_SELECTOR}; + #ifdef HAVE_ETH2 static const uint8_t ETH2_DEPOSIT_SELECTOR[SELECTOR_SIZE] = {0x22, 0x89, 0x51, 0x18}; @@ -99,6 +111,12 @@ const internalEthPlugin_t INTERNAL_ETH_PLUGINS[] = { "-erc20", erc20_plugin_call}, + {NULL, + (const uint8_t**) COMPOUND_SELECTORS, + NUM_COMPOUND_SELECTORS, + "-cmpd", + compound_plugin_call}, + #ifdef HAVE_ETH2 {NULL, (const uint8_t**) ETH2_SELECTORS, NUM_ETH2_SELECTORS, "-eth2", eth2_plugin_call}, diff --git a/src/eth_plugin_internal.h b/src/eth_plugin_internal.h index 1ad8382..4d26130 100644 --- a/src/eth_plugin_internal.h +++ b/src/eth_plugin_internal.h @@ -29,6 +29,9 @@ typedef struct internalEthPlugin_t { #define NUM_ERC20_SELECTORS 2 extern const uint8_t* const ERC20_SELECTORS[NUM_ERC20_SELECTORS]; +#define NUM_COMPOUND_SELECTORS 4 +extern const uint8_t* const COMPOUND_SELECTORS[NUM_COMPOUND_SELECTORS]; + #ifdef HAVE_ETH2 #define NUM_ETH2_SELECTORS 1 diff --git a/src_plugins/compound/compound_plugin.c b/src_plugins/compound/compound_plugin.c new file mode 100644 index 0000000..cbb68dd --- /dev/null +++ b/src_plugins/compound/compound_plugin.c @@ -0,0 +1,243 @@ +#include +#include "eth_plugin_interface.h" +#include "shared_context.h" // TODO : rewrite as independant code +#include "eth_plugin_internal.h" // TODO : rewrite as independant code +#include "utils.h" +#include "ethUtils.h" + +typedef enum { + COMPOUND_REDEEM_UNDERLYING = 0, + COMPOUND_REDEEM, + COMPOUND_MINT, + CETH_MINT +} compoundSelector_t; + +static const uint8_t COMPOUND_EXPECTED_DATA_SIZE[] = { + 4 + 32, + 4 + 32, + 4 + 32, + 4, +}; + +// redeemUnderlying : redeemAmount (32) +// redeem underlying token +// redeem : redeemTokens (32) +// redeem Ctoken +// mint : mintAmount (32) +// lend some token +// mint : +// lend some Ether + +typedef struct compound_parameters_t { + uint8_t selectorIndex; + uint8_t amount[32]; + char ticker_1[MAX_TICKER_LEN]; + uint8_t decimals; +} compound_parameters_t; + +typedef struct underlying_asset_decimals_t { + char c_ticker[MAX_TICKER_LEN]; + uint8_t decimals; +} underlying_asset_decimals_t; + +/* Sadly, we don't have the information about the underlying asset's decimals, which can differ from +the cToken decimals. Therefore, we hardcode a binding table. If Compound adds a lot of token in the +future, we will have to move to a CAL based architecture instead, as this one doesn't scale well.*/ +#define NUM_COMPOUND_BINDINGS 9 +const underlying_asset_decimals_t UNDERLYING_ASSET_DECIMALS[NUM_COMPOUND_BINDINGS] = { + {"cDAI", 18}, + {"CETH", 18}, + {"CUSDC", 6}, + {"CZRX", 18}, + {"CUSDT", 6}, + {"CBTC", 8}, + {"CBAT", 18}, + {"CREP", 18}, + {"cSAI", 18}, +}; + +bool get_underlying_asset_decimals(char *compound_ticker, uint8_t *out_decimals) { + for (size_t i = 0; i < NUM_COMPOUND_BINDINGS; i++) { + underlying_asset_decimals_t *binding = + (underlying_asset_decimals_t *) PIC(&UNDERLYING_ASSET_DECIMALS[i]); + if (strncmp(binding->c_ticker, + compound_ticker, + strnlen(binding->c_ticker, MAX_TICKER_LEN)) == 0) { + *out_decimals = binding->decimals; + return true; + } + } + return false; +} + +void compound_plugin_call(int message, void *parameters) { + switch (message) { + case ETH_PLUGIN_INIT_CONTRACT: { + ethPluginInitContract_t *msg = (ethPluginInitContract_t *) parameters; + compound_parameters_t *context = (compound_parameters_t *) msg->pluginContext; + size_t i; + for (i = 0; i < NUM_COMPOUND_SELECTORS; i++) { + if (memcmp((uint8_t *) PIC(COMPOUND_SELECTORS[i]), msg->selector, SELECTOR_SIZE) == + 0) { + context->selectorIndex = i; + break; + } + } + // enforce that ETH amount should be 0, except in ceth.mint case + if (!allzeroes(msg->pluginSharedRO->txContent->value.value, 32)) { + if (context->selectorIndex != CETH_MINT) { + PRINTF("Eth amount is not zero and token minted is not CETH!\n"); + msg->result = ETH_PLUGIN_RESULT_ERROR; + break; + } + } + if (i == NUM_COMPOUND_SELECTORS) { + PRINTF("Unknown selector %.*H\n", SELECTOR_SIZE, msg->selector); + msg->result = ETH_PLUGIN_RESULT_ERROR; + break; + } + if (msg->dataSize != COMPOUND_EXPECTED_DATA_SIZE[context->selectorIndex]) { + PRINTF("Unexpected data size for command %d expected %d got %d\n", + context->selectorIndex, + COMPOUND_EXPECTED_DATA_SIZE[context->selectorIndex], + msg->dataSize); + msg->result = ETH_PLUGIN_RESULT_ERROR; + break; + } + if (context->selectorIndex == CETH_MINT) { + // ETH amount 0x1234 is stored 0x12340000...000 instead of 0x00....001234, so we + // strip the following zeroes when copying + memset(context->amount, 0, sizeof(context->amount)); + memmove(context->amount + sizeof(context->amount) - + msg->pluginSharedRO->txContent->value.length, + msg->pluginSharedRO->txContent->value.value, + msg->pluginSharedRO->txContent->value.length); + } + PRINTF("compound plugin inititialized\n"); + msg->result = ETH_PLUGIN_RESULT_OK; + } break; + + case ETH_PLUGIN_PROVIDE_PARAMETER: { + ethPluginProvideParameter_t *msg = (ethPluginProvideParameter_t *) parameters; + compound_parameters_t *context = (compound_parameters_t *) msg->pluginContext; + PRINTF("compound plugin provide parameter %d %.*H\n", + msg->parameterOffset, + 32, + msg->parameter); + if (context->selectorIndex != CETH_MINT) { + switch (msg->parameterOffset) { + case 4: + memmove(context->amount, msg->parameter, 32); + msg->result = ETH_PLUGIN_RESULT_OK; + break; + default: + PRINTF("Unhandled parameter offset\n"); + msg->result = ETH_PLUGIN_RESULT_ERROR; + break; + } + } else { + PRINTF("CETH contract expects no parameters\n"); + msg->result = ETH_PLUGIN_RESULT_ERROR; + } + } break; + + case ETH_PLUGIN_FINALIZE: { + ethPluginFinalize_t *msg = (ethPluginFinalize_t *) parameters; + PRINTF("compound plugin finalize\n"); + msg->tokenLookup1 = msg->pluginSharedRO->txContent->destination; + msg->numScreens = 2; + msg->uiType = ETH_UI_TYPE_GENERIC; + msg->result = ETH_PLUGIN_RESULT_OK; + } break; + + case ETH_PLUGIN_PROVIDE_INFO: { + ethPluginProvideInfo_t *msg = (ethPluginProvideInfo_t *) parameters; + compound_parameters_t *context = (compound_parameters_t *) msg->pluginContext; + PRINTF("compound plugin provide token: %d\n", (msg->item1 != NULL)); + if (msg->item1 != NULL) { + strlcpy(context->ticker_1, msg->item1->token.ticker, MAX_TICKER_LEN); + switch (context->selectorIndex) { + case COMPOUND_REDEEM_UNDERLYING: + case COMPOUND_MINT: + case CETH_MINT: + msg->result = + get_underlying_asset_decimals(context->ticker_1, &context->decimals) + ? ETH_PLUGIN_RESULT_OK + : ETH_PLUGIN_RESULT_FALLBACK; + break; + + // Only case where we use the compound contract decimals + case COMPOUND_REDEEM: + context->decimals = msg->item1->token.decimals; + msg->result = ETH_PLUGIN_RESULT_OK; + break; + + default: + msg->result = ETH_PLUGIN_RESULT_FALLBACK; + break; + } + } else { + msg->result = ETH_PLUGIN_RESULT_FALLBACK; + } + } break; + + case ETH_PLUGIN_QUERY_CONTRACT_ID: { + ethQueryContractID_t *msg = (ethQueryContractID_t *) parameters; + compound_parameters_t *context = (compound_parameters_t *) msg->pluginContext; + strlcpy(msg->name, "Type", msg->nameLength); + switch (context->selectorIndex) { + case COMPOUND_REDEEM_UNDERLYING: + case COMPOUND_REDEEM: + strlcpy(msg->version, "Redeem", msg->versionLength); + break; + + case COMPOUND_MINT: + case CETH_MINT: + strlcpy(msg->version, "Lend", msg->versionLength); + break; + + default: + break; + } + strlcat(msg->version, " Assets", msg->versionLength); + msg->result = ETH_PLUGIN_RESULT_OK; + } break; + + case ETH_PLUGIN_QUERY_CONTRACT_UI: { + ethQueryContractUI_t *msg = (ethQueryContractUI_t *) parameters; + compound_parameters_t *context = (compound_parameters_t *) msg->pluginContext; + switch (msg->screenIndex) { + case 0: { + strlcpy(msg->title, "Amount", msg->titleLength); + char *ticker_ptr = context->ticker_1; + /* skip "c" in front of cToken unless we use "redeem", as + redeem is the only operation dealing with a cToken amount */ + if (context->selectorIndex != COMPOUND_REDEEM) { + ticker_ptr++; + } + amountToString(context->amount, + sizeof(context->amount), + context->decimals, + ticker_ptr, + msg->msg, + 100); + msg->result = ETH_PLUGIN_RESULT_OK; + } break; + + case 1: + strlcpy(msg->title, "Contract", msg->titleLength); + strlcpy(msg->msg, "Compound ", msg->msgLength); + strlcat(msg->msg, + context->ticker_1 + 1, + msg->msgLength); // remove the 'c' char at beginning of compound ticker + msg->result = ETH_PLUGIN_RESULT_OK; + break; + default: + break; + } + } break; + + default: + PRINTF("Unhandled message %d\n", message); + } +}