4

Overview

We are trying to test Ink! functions that leverage optional function arguments. Yet, it seems that the typescript tests don't play nicely with optional types. The encoding system is not resolving this and instead giving us errors around the length of these types.

Example

/// Used to unwrap/burn the wrapper token on behalf of a sender.
///
/// * `token_address` - is the optional address of PSP22 to unwrap into,
/// * `amount` - is the amount of tokens to burn
/// * `recipient` is the address to transfer to
#[ink(message, payable)]
pub fn unwrap_and_send_to(
    &mut self,
    token_address: Option<AccountId>,
    amount: Balance,
    recipient: AccountId,
) -> Result<()> {
    self.is_valid_unwrapping(token_address, amount)?;

    self.do_unwrap(
        token_address.clone(),
        recipient,
        self.env().caller(),
        amount,
    )?;

    Ok(())
}

We want to test this using redspot, using the flow.

const optionalAddress = api.createType('Option<AccountId>', tokenContract.address);
// now do unwrapping
let unwrapFunction =
  await tokenWrapperContract.tx.unwrapAndSendTo(
    optionalAddress,
    1,
    FerdieSigner.address,
    { value: 10 }
  );

Yet we hit the error:

     Error: createType(Option<AccountId>):: Invalid AccountId provided, expected 32 bytes, found 31

The error persists even if we don't use api.createType('Option<AccountId>', ...) and if we pass the address in directly. If we remove optional types from the Ink! function and pass in directly, the test succeeds. Any insight into how to handle optional types or should we avoid this entirely?

1 Answer 1

5

It would depend on what tokenContract.address is. If in Uint8Array format, it would expect it to be correctly encoded for the specified type. Consider the following -

// some address grabbed from Kusama, ss58 format
const SS = 'DtbB3Uy8zX9khHRf7SsSVk7esY5AngKvC9EJVhpjSzza2MU';

// same address as above, in raw format
const U8A = new Uint8Array([
  58,43,90,168,255,212,227,197,
  204,177,29,52,40,103,217,100,
  65,77,211,110,19,141,228,102,
  245,250,109,134,95,25,214,101
]);

// as above, hex-encoded
const HEX = '0x3a2b5aa8ffd4e3c5ccb11d342867d964414dd36e138de466f5fa6d865f19d665';

// this works - the Option will see non-null and handle correctly
console.log(api.createType('Option<AccountId>', SS).toU8a())

// this fails, same error as you reported - since the input is
// a Uint8Array, it expects it to be fully SCALE encoded. The
// first byte would be used for Some/None, the remainder (now 31)
// for the address
console.log(api.createType('Option<AccountId>', U8A).toU8a())

// this works - hex is handled like ss58, so it detects/converts
console.log(api.createType('Option<AccountId>', HEX).toU8a())

So in the case where a Uint8Array is passed, ensure it is an actual encoded Option<AccountId>, that would mean [1, ... address bytes], where the initial 1 indicated Some as per the SCALE encoding.

So in broad terms the API will try and convert the input as best it can - in the case of Uint8Array however, it always expects a correctly encoded SCALE value, generally these are used as part of stream decoding where Uint8Arrays are passed through.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.