Skip to content

Commit

Permalink
feat(init_contract): implement init contract instruction and tests
Browse files Browse the repository at this point in the history
- Add new instruction INS_INIT_CONTRACT (0x07) for contract initialization
- Create initContract.c/.h files with handler implementation
- Add UI flow for displaying contract amount and module reference
- Add new error codes for name/params length and module ref validation
- Implement chunked processing for contract name and parameters
- Add Python test client support for init contract instruction
- Add test case with snapshots for contract initialization flow
- Update handler.c and globals.h to support new instruction

The implementation allows users to initialize contracts by providing the
amount, module reference, contract name, and parameters, with appropriate
UI confirmation steps and chunked data processing for larger inputs.
  • Loading branch information
keiff3r committed Dec 11, 2024
1 parent 9e619bb commit 8acba8c
Show file tree
Hide file tree
Showing 26 changed files with 196 additions and 8 deletions.
3 changes: 3 additions & 0 deletions src/common/handler.c
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ int handler(uint8_t INS,
case INS_DEPLOY_MODULE:
handleDeployModule(cdata, p1, lc);
break;
case INS_INIT_CONTRACT:
handleInitContract(cdata, p1, lc);
break;
default:
THROW(ERROR_INVALID_INSTRUCTION);
break;
Expand Down
2 changes: 2 additions & 0 deletions src/common/handler.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@

#define INS_DEPLOY_MODULE 0x06

#define INS_INIT_CONTRACT 0x07

#define INS_ENCRYPTED_AMOUNT_TRANSFER 0x10
#define INS_TRANSFER_TO_ENCRYPTED 0x11
#define INS_TRANSFER_TO_PUBLIC 0x12
Expand Down
4 changes: 3 additions & 1 deletion src/common/responseCodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ enum {
ERROR_FAILED_CX_OPERATION = 0x6B07,
ERROR_INVALID_INSTRUCTION = 0x6D00,
ERROR_INVALID_SOURCE_LENGTH = 0x6B08,

ERROR_INVALID_NAME_LENGTH = 0x6B0A,
ERROR_INVALID_PARAMS_LENGTH = 0x6B0B,
ERROR_INVALID_MODULE_REF = 0x6B09,
// Error codes from the Ledger firmware
ERROR_DEVICE_LOCKED = 0x530C,
SW_WRONG_DATA_LENGTH = 0x6A87
Expand Down
1 change: 1 addition & 0 deletions src/common/ui/display.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,4 @@ void uiSignScheduledTransferPairFlowSignDisplay(void);
void uiSignScheduledTransferPairFlowDisplay(void);

void uiDeployModuleDisplay(void);
void uiInitContractDisplay(void);
19 changes: 19 additions & 0 deletions src/common/ui/display_bagl.c
Original file line number Diff line number Diff line change
Expand Up @@ -813,4 +813,23 @@ void uiDeployModuleDisplay() {
ux_flow_init(0, ux_deploy_module, NULL);
}

// Init Contract
UX_STEP_NOCB(ux_init_contract_1_step,
bnnn_paging,
{.title = "Amount", .text = (char *) global.initContract.amountDisplay});
UX_STEP_NOCB(ux_init_contract_2_step,
bnnn_paging,
{.title = "Module ref", .text = (char *) global.initContract.moduleRefDisplay});
UX_FLOW(ux_init_contract,
&ux_sign_flow_shared_review,
&ux_sign_flow_account_sender_view,
&ux_init_contract_1_step,
&ux_init_contract_2_step,
&ux_sign_flow_shared_sign,
&ux_sign_flow_shared_decline);

void uiInitContractDisplay() {
ux_flow_init(0, ux_init_contract, NULL);
}

#endif
2 changes: 2 additions & 0 deletions src/globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "signTransferWithSchedule.h"
#include "signRegisterData.h"
#include "deployModule.h"
#include "initContract.h"
#include "ux.h"

#define LEGACY_PURPOSE 1105
Expand Down Expand Up @@ -125,6 +126,7 @@ typedef union {
signConfigureBaker_t signConfigureBaker;
signConfigureDelegationContext_t signConfigureDelegation;
deployModule_t deployModule;
initContract_t initContract;
transactionWithDataBlob_t withDataBlob;
} instructionContext;
extern instructionContext global;
Expand Down
93 changes: 93 additions & 0 deletions src/initContract.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#include "os.h"
#include "format.h"
#include "common/ui/display.h"
#include "common/responseCodes.h"
#include "common/sign.h"
#include "common/util.h"
#include "initContract.h"

// TODO: ADAPT THIS TO THE NEW INSTRUCTION

static initContract_t *ctx_init_contract = &global.initContract;
static tx_state_t *tx_state = &global_tx_state;

#define P1_INITIAL 0x00
#define P1_NAME 0x01
#define P1_PARAMS 0x02

void handleInitContract(uint8_t *cdata, uint8_t p1, uint8_t lc) {
if (p1 == P1_INITIAL) {
cx_sha256_init(&tx_state->hash);
cdata += parseKeyDerivationPath(cdata);
cdata += hashAccountTransactionHeaderAndKind(cdata, INIT_CONTRACT);
// hash the amount
updateHash((cx_hash_t *) &tx_state->hash, cdata, 8);
// extract the amount
ctx_init_contract->amount = U8BE(cdata, 0);
// Format the amount
amountToGtuDisplay((uint8_t *) ctx_init_contract->amountDisplay,
sizeof(ctx_init_contract->amountDisplay),
ctx_init_contract->amount);
cdata += 8;
// hash the module ref
updateHash((cx_hash_t *) &tx_state->hash, cdata, 32);
// extract the module ref
memmove(ctx_init_contract->moduleRef, cdata, 32);
// Format the module ref
if (format_hex(ctx_init_contract->moduleRef, 32, ctx_init_contract->moduleRefDisplay, 65) ==
-1) {
THROW(ERROR_INVALID_MODULE_REF);
}
ctx_init_contract->state = INIT_CONTRACT_NAME_FIRST;
sendSuccessNoIdle();
}

else if (p1 == P1_NAME) {
uint8_t lengthSize = 2;
if (ctx_init_contract->state == INIT_CONTRACT_NAME_FIRST) {
// extract the name length
ctx_init_contract->nameLength = U2BE(cdata, 0);
// calculate the remaining name length
ctx_init_contract->remainingNameLength = ctx_init_contract->nameLength + lengthSize;
// set the state to the next state
ctx_init_contract->state = INIT_CONTRACT_NAME_NEXT;
} else if (ctx_init_contract->remainingNameLength < lc) {
THROW(ERROR_INVALID_NAME_LENGTH);
}
// hash the whole chunk
updateHash((cx_hash_t *) &tx_state->hash, cdata, lc);
// subtract the length of the chunk from the remaining name length
ctx_init_contract->remainingNameLength -= lc;
if (ctx_init_contract->remainingNameLength > 0) {
sendSuccessNoIdle();
} else if (ctx_init_contract->remainingNameLength == 0) {
ctx_init_contract->state = INIT_CONTRACT_PARAMS_FIRST;
sendSuccessNoIdle();
}

} else if (p1 == P1_PARAMS) {
uint8_t lengthSize = 2;
if (ctx_init_contract->state == INIT_CONTRACT_PARAMS_FIRST) {
// extract the params length
ctx_init_contract->paramsLength = U2BE(cdata, 0);
// calculate the remaining params length
ctx_init_contract->remainingParamsLength = ctx_init_contract->paramsLength + lengthSize;
// set the state to the next state
ctx_init_contract->state = INIT_CONTRACT_PARAMS_NEXT;
} else if (ctx_init_contract->remainingParamsLength < lc) {
THROW(ERROR_INVALID_PARAMS_LENGTH);
}
// hash the whole chunk
updateHash((cx_hash_t *) &tx_state->hash, cdata, lc);
// subtract the length of the chunk from the remaining params length
ctx_init_contract->remainingParamsLength -= lc;
if (ctx_init_contract->remainingParamsLength > 0) {
sendSuccessNoIdle();
} else if (ctx_init_contract->remainingParamsLength == 0) {
uiInitContractDisplay();
}

} else {
THROW(ERROR_INVALID_STATE);
}
}
39 changes: 39 additions & 0 deletions src/initContract.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#pragma once

#include <stdint.h>
#include <stdbool.h>

// TODO: ADAPT THIS TO THE NEW INSTRUCTION

typedef enum {
INIT_CONTRACT_INITIAL = 60,
INIT_CONTRACT_NAME_FIRST = 61,
INIT_CONTRACT_NAME_NEXT = 62,
INIT_CONTRACT_PARAMS_FIRST = 63,
INIT_CONTRACT_PARAMS_NEXT = 64,
INIT_CONTRACT_END = 65
} initContractState_t;

/**
* Handles the INIT_CONTRACT instruction, which initializes a contract
*
*
*/
void handleInitContract(uint8_t *cdata, uint8_t p1, uint8_t lc);

typedef struct {
uint64_t amount;
uint8_t moduleRef[32];
char amountDisplay[21];
char moduleRefDisplay[65];
uint32_t nameLength;
uint32_t remainingNameLength;
uint32_t paramsLength;
uint32_t remainingParamsLength;
initContractState_t state;
} initContract_t;

// typedef struct {
// uint8_t version[32];
// uint8_t sourceLength[32];
// } deployModuleBlob_t;

Check notice

Code scanning / CodeQL

Commented-out code Note

This comment appears to contain commented-out code.
35 changes: 30 additions & 5 deletions tests/application_client/boilerplate_command_sender.py
Original file line number Diff line number Diff line change
Expand Up @@ -806,13 +806,21 @@ def init_contract(
name: bytes,
params: bytes,
) -> Generator[None, None, None]:
print("km-------- Starting init_contract")
print(f"km-------- Path: {path}")
print(f"km-------- Amount: {amount}")
print(f"km-------- Module ref: {module_ref.hex()}")
print(f"km-------- Name: {name}")
print(f"km-------- Params: {params.hex()}")

if amount > 0xFFFFFFFFFFFFFFFF:
raise ValueError("Amount must be less than 18446744073709551615")
# Send the initial data
data = pack_derivation_path(path)
data += header_and_type
data += amount.to_bytes(8, byteorder="big")
data += module_ref
print(f"km-------- Initial data: {data.hex()}")
temp_response = self.backend.exchange(
cla=CLA,
ins=InsType.INIT_CONTRACT,
Expand All @@ -825,36 +833,53 @@ def init_contract(
# Send the name
data = len(name).to_bytes(2, byteorder="big")
name_chunks = split_message(name, MAX_APDU_LEN)
for chunk in name_chunks:
print(f"km-------- Name chunks: {len(name_chunks)}")
for i, chunk in enumerate(name_chunks):
if i == 0:
data += chunk
else:
data == chunk
print(f"km-------- Sending name chunk: {data.hex()}")
temp_response = self.backend.exchange(
cla=CLA,
ins=InsType.INIT_CONTRACT,
p1=P1.P1_INIT_CONTRACT_NAME,
p2=P2.P2_NONE,
data=chunk,
data=data,
)
if temp_response.status != 0x9000:
raise ExceptionRAPDU(temp_response.status)
# Send the params
data = len(params).to_bytes(2, byteorder="big")
params_chunks = split_message(params, MAX_APDU_LEN)
last_chunk = params_chunks.pop()
for chunk in params_chunks:
print(f"km-------- Params chunks: {len(params_chunks) + 1}")
for i, chunk in enumerate(params_chunks):
if i == 0:
data += chunk
else:
data == chunk
print(f"km-------- Sending params chunk: {data.hex()}")
temp_response = self.backend.exchange(
cla=CLA,
ins=InsType.INIT_CONTRACT,
p1=P1.P1_INIT_CONTRACT_PARAMS,
p2=P2.P2_NONE,
data=chunk,
data=data,
)
if temp_response.status != 0x9000:
raise ExceptionRAPDU(temp_response.status)
if len(params_chunks) == 0:
data = len(last_chunk).to_bytes(2, byteorder="big") + last_chunk
else:
data = last_chunk
print(f"km-------- Sending final params chunk: {data.hex()}")
with self.backend.exchange_async(
cla=CLA,
ins=InsType.INIT_CONTRACT,
p1=P1.P1_INIT_CONTRACT_PARAMS,
p2=P2.P2_NONE,
data=last_chunk,
data=data,
) as response:
yield response

Expand Down
Binary file added tests/snapshots/nanosp/test_init_contract/00000.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/snapshots/nanosp/test_init_contract/00001.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/snapshots/nanosp/test_init_contract/00002.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/snapshots/nanosp/test_init_contract/00003.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/snapshots/nanosp/test_init_contract/00004.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/snapshots/nanosp/test_init_contract/00005.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/snapshots/nanosp/test_init_contract/00006.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/snapshots/nanosp/test_init_contract/00007.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/snapshots/nanox/test_init_contract/00000.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/snapshots/nanox/test_init_contract/00001.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/snapshots/nanox/test_init_contract/00002.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/snapshots/nanox/test_init_contract/00003.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/snapshots/nanox/test_init_contract/00004.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/snapshots/nanox/test_init_contract/00005.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/snapshots/nanox/test_init_contract/00006.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/snapshots/nanox/test_init_contract/00007.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 4 additions & 2 deletions tests/test_init_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def test_init_contract(
header_and_type = bytes.fromhex(
"20a845815bd43a1999e90fbf971537a70392eb38f89e6bd32b3dd70e1a9551d7000000000000000a0000000000000064000000290000000063de5da701"
)
amount = 1000000000000000000
amount = 0xFFFFFFFFFFFFFFFF
module_ref = bytes.fromhex(
"a00000000000000000000000000000000000000000000000000000000000000a"
)
Expand All @@ -44,4 +44,6 @@ def test_init_contract(
response = client.get_async_response()
print(response.data.hex())
assert response.status == 0x9000
assert response.data == bytes.fromhex("Enter valid signature here")
assert response.data == bytes.fromhex(
"ca0e947d521063cc40c3bf8a65dd05a6bb66c799957417f94123c0642162020fed8dd80cd9aadd51f24d697bb9bcce26b72115dbd80bfe893a8395b54f8bb10c"
)

0 comments on commit 8acba8c

Please sign in to comment.