Skip to content

Commit

Permalink
correct all key + address tests
Browse files Browse the repository at this point in the history
  • Loading branch information
jleni committed May 15, 2024
1 parent 97c7547 commit 4ead055
Show file tree
Hide file tree
Showing 49 changed files with 128 additions and 113 deletions.
2 changes: 1 addition & 1 deletion app/src/addr.c
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ zxerr_t addr_getItem(int8_t displayIdx,
}

case addr_sapling_div: {
snprintf(outKey, outKeyLen, "Shielded w/div");
snprintf(outKey, outKeyLen, "Shielded div");
pageString(outVal, outValLen, (char *)(G_io_apdu_buffer + VIEW_ADDRESS_OFFSET_SAPLING), pageIdx,
pageCount);
return zxerr_ok;
Expand Down
13 changes: 11 additions & 2 deletions app/src/crypto.c
Original file line number Diff line number Diff line change
Expand Up @@ -1309,7 +1309,7 @@ typedef struct {
} tmp_sapling_fvk;

// handleGetKeyFVK: return the full viewing key for a given path
zxerr_t crypto_fvk_sapling(uint8_t *buffer, uint16_t bufferLen, uint32_t p, uint16_t *replyLen) {
zxerr_t crypto_fvk_sapling(uint8_t *buffer, uint16_t bufferLen, uint32_t zip32Account, uint16_t *replyLen) {
zemu_log_stack("crypto_fvk_sapling");

MEMZERO(buffer, bufferLen);
Expand All @@ -1320,7 +1320,7 @@ zxerr_t crypto_fvk_sapling(uint8_t *buffer, uint16_t bufferLen, uint32_t p, uint
}

// get full viewing key
zip32_fvk(p, out->fvk);
zip32_fvk(zip32Account, out->fvk);
CHECK_APP_CANARY()

*replyLen = AK_SIZE + NK_SIZE + OVK_SIZE;
Expand Down Expand Up @@ -1385,7 +1385,9 @@ typedef struct {

zxerr_t crypto_fillAddress_with_diversifier_sapling(
uint8_t *buffer, uint16_t bufferLen, uint32_t zip32Account, uint8_t *div, uint16_t *replyLen) {

if (bufferLen < sizeof(tmp_buf_addr_s)) {
ZEMU_LOGF(100, "cannot fit response\n");
return zxerr_unknown;
}

Expand All @@ -1398,6 +1400,13 @@ zxerr_t crypto_fillAddress_with_diversifier_sapling(
// Initialize diversifier
MEMCPY(out->diversifier, div, DIV_SIZE);
if (!diversifier_is_valid(out->diversifier)) {
char tmpBuf[40];
array_to_hexstr(tmpBuf, 40, out->diversifier, 11);
ZEMU_LOGF(100, "input diversifier is not valid: %s\n", tmpBuf);
array_to_hexstr(tmpBuf, 40, div, 11);
ZEMU_LOGF(100, "input diversifier is not valid: %s\n", tmpBuf);
array_to_hexstr(tmpBuf, 40, hdPath.saplingdiv_div, 11);
ZEMU_LOGF(100, "input diversifier is not valid: %s\n", tmpBuf);
return zxerr_unknown;
}

Expand Down
21 changes: 14 additions & 7 deletions app/src/handlers/handler_addr.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@


__Z_INLINE void handleGetAddrSecp256K1(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) {
zemu_log("----[handleGetAddrSecp256K1]\n");
ZEMU_LOGF(100, "----[handleGetAddrSecp256K1]\n");
*tx = 0;

extractHDPathTransparent(rx, OFFSET_DATA);
Expand All @@ -28,6 +28,7 @@ __Z_INLINE void handleGetAddrSecp256K1(volatile uint32_t *flags, volatile uint32

zxerr_t err = crypto_fillAddress_secp256k1(G_io_apdu_buffer, IO_APDU_BUFFER_SIZE - 2, &replyLen);
if (err != zxerr_ok) {
ZEMU_LOGF(100, "Err: %d\n", err);
*tx = 0;
THROW(APDU_CODE_DATA_INVALID);
}
Expand All @@ -45,21 +46,21 @@ __Z_INLINE void handleGetAddrSecp256K1(volatile uint32_t *flags, volatile uint32
}

__Z_INLINE void handleGetAddrSapling(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) {
zemu_log("----[handleGetAddrSapling]\n");
ZEMU_LOGF(100, "----[handleGetAddrSapling]\n");
*tx = 0;

if (rx < APDU_MIN_LENGTH) {
zemu_log("Missing data!\n");
ZEMU_LOGF(100, "rx: %d\n", rx);
THROW(APDU_CODE_COMMAND_NOT_ALLOWED);
}

if (rx != (uint32_t)(APDU_DATA_LENGTH_GET_ADDR_SAPLING + APDU_MIN_LENGTH)) {
zemu_log("Wrong length!\n");
ZEMU_LOGF(100, "rx: %d\n", rx);
THROW(APDU_CODE_COMMAND_NOT_ALLOWED);
}

if (G_io_apdu_buffer[OFFSET_DATA_LEN] != APDU_DATA_LENGTH_GET_ADDR_SAPLING) {
zemu_log("Wrong offset data length!\n");
ZEMU_LOGF(100, "len: %d\n", G_io_apdu_buffer[OFFSET_DATA_LEN]);
THROW(APDU_CODE_COMMAND_NOT_ALLOWED);
}

Expand All @@ -70,6 +71,7 @@ __Z_INLINE void handleGetAddrSapling(volatile uint32_t *flags, volatile uint32_t
uint16_t replyLen = 0;
zxerr_t err = crypto_fillAddress_sapling(G_io_apdu_buffer, IO_APDU_BUFFER_SIZE - 2, hdPath.sapling_path[2], &replyLen);
if (err != zxerr_ok) {
ZEMU_LOGF(100, "Err: %d\n", err);
*tx = 0;
THROW(APDU_CODE_DATA_INVALID);
}
Expand All @@ -87,18 +89,21 @@ __Z_INLINE void handleGetAddrSapling(volatile uint32_t *flags, volatile uint32_t
}

__Z_INLINE void handleGetAddrSaplingDiv(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) {
zemu_log("----[handleGetAddrSapling_withdiv]\n");
ZEMU_LOGF(100, "----[handleGetAddrSapling_withdiv]\n");

*tx = 0;
if (rx < APDU_MIN_LENGTH) {
ZEMU_LOGF(100, "rx: %d\n", rx);
THROW(APDU_CODE_COMMAND_NOT_ALLOWED);
}

if (rx - APDU_MIN_LENGTH != APDU_DATA_LENGTH_GET_ADDR_DIV) {
ZEMU_LOGF(100, "rx: %d\n", rx);
THROW(APDU_CODE_COMMAND_NOT_ALLOWED);
}

if (G_io_apdu_buffer[OFFSET_DATA_LEN] != APDU_DATA_LENGTH_GET_ADDR_DIV) {
ZEMU_LOGF(100, "len: %d\n", G_io_apdu_buffer[OFFSET_DATA_LEN]);
THROW(APDU_CODE_COMMAND_NOT_ALLOWED);
}

Expand All @@ -111,9 +116,11 @@ __Z_INLINE void handleGetAddrSaplingDiv(volatile uint32_t *flags, volatile uint3
zxerr_t err = crypto_fillAddress_with_diversifier_sapling(
G_io_apdu_buffer, IO_APDU_BUFFER_SIZE - 3,
hdPath.saplingdiv_path[2],
hdPath.saplingdiv_div, &replyLen);
hdPath.saplingdiv_div,
&replyLen);

if (err != zxerr_ok) {
ZEMU_LOGF(100, "Err: %d\n", err);
*tx = 0;
THROW(APDU_CODE_DATA_INVALID);
}
Expand Down
29 changes: 12 additions & 17 deletions app/src/handlers/handler_keys.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,8 @@ __Z_INLINE void handleGetKeyIVK(volatile uint32_t *flags, volatile uint32_t *tx,
zemu_log("----[handleGetKeyIVK]\n");

*tx = 0;
if (rx < APDU_MIN_LENGTH || rx - APDU_MIN_LENGTH != APDU_DATA_LENGTH_GET_IVK ||
G_io_apdu_buffer[OFFSET_DATA_LEN] != APDU_DATA_LENGTH_GET_IVK || G_io_apdu_buffer[OFFSET_P1] == 0) {
zemu_log("Wrong length!\n");
if (rx - APDU_MIN_LENGTH != APDU_DATA_LENGTH_GET_IVK) {
ZEMU_LOGF(100, "Wrong length! %d\n", rx);
THROW(APDU_CODE_COMMAND_NOT_ALLOWED);
}

Expand All @@ -45,7 +44,7 @@ __Z_INLINE void handleGetKeyIVK(volatile uint32_t *flags, volatile uint32_t *tx,
key_state.len = (uint8_t)replyLen;

view_review_init(key_getItem, key_getNumItems, app_reply_key);
view_review_show(REVIEW_TXN);
view_review_show(REVIEW_KEYS);
*flags |= IO_ASYNCH_REPLY;
}

Expand All @@ -55,9 +54,8 @@ __Z_INLINE void handleGetKeyOVK(volatile uint32_t *flags, volatile uint32_t *tx,
zemu_log("----[handleGetKeyOVK]\n");

*tx = 0;
if (rx < APDU_MIN_LENGTH || rx - APDU_MIN_LENGTH != APDU_DATA_LENGTH_GET_OVK ||
G_io_apdu_buffer[OFFSET_DATA_LEN] != APDU_DATA_LENGTH_GET_OVK || G_io_apdu_buffer[OFFSET_P1] == 0) {
zemu_log("Wrong length!\n");
if (rx - APDU_MIN_LENGTH != APDU_DATA_LENGTH_GET_OVK) {
ZEMU_LOGF(100, "Wrong length! %d\n", rx);
THROW(APDU_CODE_COMMAND_NOT_ALLOWED);
}

Expand All @@ -74,7 +72,7 @@ __Z_INLINE void handleGetKeyOVK(volatile uint32_t *flags, volatile uint32_t *tx,
key_state.len = (uint8_t)replyLen;

view_review_init(key_getItem, key_getNumItems, app_reply_key);
view_review_show(REVIEW_TXN);
view_review_show(REVIEW_KEYS);
*flags |= IO_ASYNCH_REPLY;
}

Expand All @@ -83,9 +81,8 @@ __Z_INLINE void handleGetKeyFVK(volatile uint32_t *flags, volatile uint32_t *tx,
zemu_log("----[handleGetKeyFVK]\n");

*tx = 0;
if (rx < APDU_MIN_LENGTH || rx - APDU_MIN_LENGTH != APDU_DATA_LENGTH_GET_FVK ||
G_io_apdu_buffer[OFFSET_DATA_LEN] != APDU_DATA_LENGTH_GET_FVK || G_io_apdu_buffer[OFFSET_P1] == 0) {
zemu_log("Wrong length!\n");
if (rx - APDU_MIN_LENGTH != APDU_DATA_LENGTH_GET_FVK) {
ZEMU_LOGF(100, "Wrong length! %d\n", rx);
THROW(APDU_CODE_COMMAND_NOT_ALLOWED);
}

Expand All @@ -107,7 +104,7 @@ __Z_INLINE void handleGetKeyFVK(volatile uint32_t *flags, volatile uint32_t *tx,
key_state.len = (uint8_t)replyLen;

view_review_init(key_getItem, key_getNumItems, app_reply_key);
view_review_show(REVIEW_TXN);
view_review_show(REVIEW_KEYS);
*flags |= IO_ASYNCH_REPLY;
}

Expand Down Expand Up @@ -151,23 +148,21 @@ __Z_INLINE void handleGetNullifier(volatile uint32_t *flags, volatile uint32_t *
key_state.len = (uint8_t)replyLen;

view_review_init(key_getItem, key_getNumItems, app_reply_key);
view_review_show(REVIEW_TXN);
view_review_show(REVIEW_KEYS);
*flags |= IO_ASYNCH_REPLY;
}

__Z_INLINE void handleGetDiversifierList(volatile uint32_t *tx, uint32_t rx) {
zemu_log("----[handleGetDiversifierList]\n");

*tx = 0;
if (rx < APDU_MIN_LENGTH) {
THROW(APDU_CODE_COMMAND_NOT_ALLOWED);
}

if (rx - APDU_MIN_LENGTH != APDU_DATA_LENGTH_GET_DIV_LIST) {
ZEMU_LOGF(100, "incorrect input size %d\n", rx);
THROW(APDU_CODE_COMMAND_NOT_ALLOWED);
}

if (G_io_apdu_buffer[OFFSET_DATA_LEN] != APDU_DATA_LENGTH_GET_DIV_LIST) {
zemu_log_stack("payload too small");
THROW(APDU_CODE_COMMAND_NOT_ALLOWED);
}

Expand Down
2 changes: 2 additions & 0 deletions app/src/handlers/handler_path.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ __Z_INLINE void extractHDPathSaplingDiv(uint32_t rx, uint32_t offset) {
THROW(APDU_CODE_DATA_INVALID);
}

MEMCPY(hdPath.saplingdiv_div, ctx.buffer + ctx.offset, DIV_SIZE);

// Validate data
if ((hdPath.saplingdiv_path[2] & MASK_HARDENED) == 0) {
THROW(APDU_CODE_DATA_INVALID);
Expand Down
2 changes: 1 addition & 1 deletion deps/ledger-zxlib
8 changes: 4 additions & 4 deletions js/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ export default class ZCashApp extends GenericApp {

try {
const p1 = showInScreen ? P1_VALUES.SHOW_ADDRESS_IN_DEVICE : P1_VALUES.ONLY_RETRIEVE
const responseBuffer = await this.transport.send(CLA, INS.GET_ADDR_SAPLING, p1, 0, sentToDevice)
const responseBuffer = await this.transport.send(CLA, INS.GET_ADDR_SAPLING_DIV, p1, 0, sentToDevice)
const response = processResponse(responseBuffer)

const addressRaw = response.readBytes(SAPLING_ADDR_LEN)
Expand Down Expand Up @@ -189,7 +189,7 @@ export default class ZCashApp extends GenericApp {
sentToDevice.writeUInt32LE(zip32Account, 0)

try {
const responseBuffer = await this.transport.send(CLA, INS.GET_OVK_SAPLING, 0, 0, sentToDevice, [
const responseBuffer = await this.transport.send(CLA, INS.GET_FVK_SAPLING, 0, 0, sentToDevice, [
0x9000,
])
const response = processResponse(responseBuffer)
Expand Down Expand Up @@ -225,9 +225,9 @@ export default class ZCashApp extends GenericApp {
const responseBuffer = await this.transport.send(
CLA,
INS.GET_DIV_LIST,
P1_VALUES.ONLY_RETRIEVE,
0,
Buffer.concat([sentToDevice, startingDiversifier]),
0,
sentToDevice,
)
const response = processResponse(responseBuffer)

Expand Down
Binary file modified tests_zemu/snapshots/s-get-fvk/00000.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests_zemu/snapshots/s-get-fvk/00001.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests_zemu/snapshots/sp-get-fvk/00001.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests_zemu/snapshots/sp-get-fvk/00002.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests_zemu/snapshots/st-get-fvk/00000.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests_zemu/snapshots/st-get-fvk/00001.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests_zemu/snapshots/st-get-fvk/00002.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests_zemu/snapshots/st-get-ivk/00000.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests_zemu/snapshots/st-get-ivk/00001.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests_zemu/snapshots/st-get-ivk/00002.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests_zemu/snapshots/st-get-ovk/00000.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests_zemu/snapshots/st-get-ovk/00001.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests_zemu/snapshots/st-get-ovk/00002.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests_zemu/snapshots/x-get-fvk/00001.png
Binary file modified tests_zemu/snapshots/x-get-fvk/00002.png
6 changes: 3 additions & 3 deletions tests_zemu/tests/_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ const APP_PATH_ST = resolve('../app/output/app_stax.elf')

export const models: IDeviceModel[] = [
{ name: 'nanos', prefix: 'S', path: APP_PATH_S },
// { name: 'nanox', prefix: 'X', path: APP_PATH_X },
// { name: 'nanosp', prefix: 'SP', path: APP_PATH_SP },
// { name: 'stax', prefix: 'ST', path: APP_PATH_ST },
{ name: 'nanox', prefix: 'X', path: APP_PATH_X },
{ name: 'nanosp', prefix: 'SP', path: APP_PATH_SP },
{ name: 'stax', prefix: 'ST', path: APP_PATH_ST },
]

export const defaultOptions = {
Expand Down
105 changes: 30 additions & 75 deletions tests_zemu/tests/addresses.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,79 +128,34 @@ describe('Addresses', function() {
}
})

test.concurrent.each(models)('get shielded address with div', async function (m) {
const sim = new Zemu(m.path)
try {
await sim.start({ ...defaultOptions, model: m.name })
const app = new ZCashApp(sim.getTransport())

const path = 1000
const div = Buffer.from('c69e979c6763c1b09238dc', 'hex')

const addr = await app.getAddrDivSapling(path, div)
console.log(addr)
expect(addr.returnCode).toEqual(0x9000)

const expected_addrRaw = 'c69e979c6763c1b09238dc6bd5dcbf35360df95dcadf8c0fa25dcbedaaf6057538b812d06656726ea27667'
const expected_addr = 'zs1c60f08r8v0qmpy3cm34ath9lx5mqm72aet0ccrazth97m2hkq46n3wqj6pn9vunw5fmxwclltd3'

const addrRaw = addr.addressRaw.toString('hex')
expect(addrRaw).toEqual(expected_addrRaw)
expect(addr.address).toEqual(expected_addr)
} finally {
await sim.close()
}
})

test.concurrent.each(models)('show shielded address with div', async function (m) {
const sim = new Zemu(m.path)
try {
await sim.start({
...defaultOptions,
model: m.name,
approveKeyword: m.name === 'stax' ? 'QR' : '',
approveAction: ButtonKind.ApproveTapButton,
})
const app = new ZCashApp(sim.getTransport())

const path = 1000
const div = Buffer.from('c69e979c6763c1b09238dc', 'hex')

const addrreq = app.showAddrDiv(path, div)
await sim.waitUntilScreenIsNot(sim.getMainMenuSnapshot(), 60000)
await sim.compareSnapshotsAndApprove('.', `${m.prefix.toLowerCase()}-show-shielded-addr`)
const addr = await addrreq

console.log(addr)
expect(addr.returnCode).toEqual(0x9000)

const expected_addrRaw = 'c69e979c6763c1b09238dc6bd5dcbf35360df95dcadf8c0fa25dcbedaaf6057538b812d06656726ea27667'

const addrRaw = addr.addressRaw.toString('hex')
expect(addrRaw).toEqual(expected_addrRaw)
} finally {
await sim.close()
}
})

test.concurrent.each(models)('get div list with startindex', async function (m) {
const sim = new Zemu(m.path)
try {
await sim.start({ ...defaultOptions, model: m.name })
const app = new ZCashApp(sim.getTransport())

const startindex = Buffer.from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])

const divlist = await app.getDivListSapling(1000, startindex)
console.log(divlist)
expect(divlist.returnCode).toEqual(0x9000)

const first_div = 'c69e979c6763c1b09238dc'

const first_divRaw = divlist.divlist[0]
expect(first_div).toEqual(first_divRaw)
} finally {
await sim.close()
}
})
test.concurrent.each(models)('get shielded address with div', async function(m) {
const sim = new Zemu(m.path)
try {
await sim.start({
...defaultOptions,
model: m.name,
approveKeyword: m.name === 'stax' ? 'QR' : '',
approveAction: ButtonKind.ApproveTapButton,
})
const app = new ZCashApp(sim.getTransport())

const zip32Account = 1000 + 0x80000000
const div = Buffer.from('71635f26c1b4a2332abeb7', 'hex')

const addrRequest = app.getAddressSamplingFromDiversifier(zip32Account, div)
// Wait until we are not in the main menu
await sim.waitUntilScreenIsNot(sim.getMainMenuSnapshot(), 600000)
await sim.compareSnapshotsAndApprove('.', `${m.prefix.toLowerCase()}-show_address_shielded_with_div`)

const addr = await addrRequest

const expected_addrRaw = '71635f26c1b4a2332abeb70b1249e61ed4e40b1cc114c1ef994dcf304e2e5945748e879660550443161cda'
const expected_addr = 'zs1w9347fkpkj3rx247ku93yj0xrm2wgzcucy2vrmuefh8nqn3wt9zhfr58jes92pzrzcwd5rrjn0g'

expect(addr?.addressRaw.toString('hex')).toEqual(expected_addrRaw)
expect(addr?.address).toEqual(expected_addr)
} finally {
await sim.close()
}
})
})

0 comments on commit 4ead055

Please sign in to comment.