After successfully completing the complicated Cross Contract quest on last post, we are ready for the Custom Types quest. Beware though, this quest is quite long and challenging, just what we like!
If you haven’t checked out the discussion on custom Soroban types, please take a look at the Using get to Retrieve Data section on the Auth Store tutorial.
Understanding README.md
Let's once again check out the README.md file for this quest.
Setting Up Our Quest Account
Use the usual commands, sq login if required, and sq play 5 to start your adventure.
Examining the Contract
Let's start with checking out the lib.rs file.
use types::*;
pub struct TypesContract;
#[contractimpl]
impl TypesContract {
pub fn c_rect(_env: Env, _rect: Rectangle) {}
pub fn c_animal(_env: Env, _animal: Animal) {}
pub fn c_user(_env: Env, _user: User) {}
pub fn c_rgb(_env: Env, _rgb: RGB) {}
pub fn c_color(_env: Env, _val: Color) {}
pub fn c_part(_env: Env, _participant: Participant) {}
pub fn c_card(_env: Env, _card: RoyalCard) {}
}
It has some empty functions! The arguments of the functions are different types, which are seemingly imported from the types.rs file, as there is the use types::*; import up top.
We won't add any functionality to this file, so let's check out the types.rs file.
Filling Out types.rs
The types.rs file already has two examples: a struct and an enum.
#[contracttype]
pub struct Cylinder {
pub radius: u32,
pub height: u32,
}
#[contracttype]
pub enum Car {
Chevrolet,
Ford,
Mercedes,
Porsche,
Honda,
Toyota
}
Using Custom Types
The
structs are used for creating objects that have multiple different values. These values can consist of basic types likeu32andBytes, anotherstructs,enums,arrays, and so on. Thestructs allow us to carry all properties of an object together on a single variable. Well, it is useful to pass aCarstruct to ainsert_to_garagefunction instead oftires,engine,color,fuel_type,license_plate,total_kilometers...The
enums are used for creating numeric types with names for a limited list. It is mostly useful for programmers, as trying to remember the ID of a student in a classroom might not be helpful, but you would know whoBobis. The compiler is not interested in those names, and will happily use their numeric values in your code.
We have a lot of empty structs and enums, waiting for us to implement them. Before starting out, please check out the Custom Types section from the Soroban Docs to learn more about the type system of Soroban.
Implementing Rectangle
Let's start with implementing the Rectangle struct.
#[contracttype]
pub struct Rectangle {
// TODO: create your fields here for your `Rectangle` type
}
From README.md, we can see that
The
Rectangletype must be astruct, with two fields:widthandheightwhich both must be au32value.
So, we can implement Rectangle as follows:
pub struct Rectangle {
pub width: u32,
pub height: u32,
}
Implementing Animal
Next, let's check out the Animal enum. From README.md, we can see that
The
Animaltype must be anenum, with at least two variations:CatandDog.
So, we can implement Animal as:
pub enum Animal {
Dog,
Cat,
Bear,
Wolf,
Gorilla /* and many more */
}
Implementing User
Let's move on to the User struct. Again, from README.md, we see that
The
Usertype must be astructwithname,age, andpetfields, corresponding toBytes,u32, andAnimalvalues, respectively.
Let's implement the User type. It consists of another struct, but that's nothing hard to implement.
pub struct User {
pub name: Bytes,
pub age: u32,
pub pet: Animal,
}
Implementing RGB
We are almost halfway done! Let's now implement the RGB struct.
The
RGBtype must be a tuplestructtype made with a tuple of 3u32values.
Since it should be defined using unnamed u32 types, we can define it as a tuple using the following definition.
#[contracttype]
pub struct RGB(pub u32, pub u32, pub u32);
Implementing Color
More than halfway done! Now, we must implement the Color enum, using the following definiton.
The
Colortype will combine theRGBcustom type nested within a tupleenumtype. Construct yourRGBstruct type as described bove, Then, yourColorenum type must be defined as a variant with a name of "RGB" and an instance of yourRGBtype.
An enums type definitions can be of any type, including custom structs.
pub enum Color {
RGB(RGB)
}
Implementing Participant
Almost done! We now need to implement the Participant enum type. The definition is
The
Participanttype must be anenumwith single-value tuple variants as follows:
- An "Account" variant with an
AccountIdtype- A "Contract" variant with a
BytesN<32>type
So, Participant should be composed from two different Soroban types: AccountId, and BytesN<32>. Since we know how to implement custom types on enums, let's go ahead and define the Participant enum.
pub enum Participant {
Contract(BytesN<32>),
Account(AccountId),
}
Implementing RoyalCard
Only one left! We now just need to implement the RoyalCard enum.
The
RoyalCardtype must be anenumcontaining threeu32integer variations as follows:
- A "Jack" variant, with a value of 11
- A "Queen" variant, with a value of 12
- A "King" variant, with a value of 13
It is quite simple to define an enum with given values, so we can implement it as follows.
#[repr(u32)]
pub enum RoyalCard {
Jack = 11,
Queen = 12,
King = 13
}
The #[repr(u32)] attribute tells the compiler to pack each enum element into an 32 bit memory slot, which is the size of an u32 variable.
We have accomplished quite much! But the quest isn't over, since we still have to invoke the "empty" functions successfully using soroban CLI. To learn more about how we can invoke those functions, let's check out the test.rs file.
Examining the Test
The test has only one function, test_types, which tests the types of all of the custom structs and enums we have created by constructing the corresponding type, and calling the corresponding function. Let's check if our implementations of the custom types are correct using
cargo test -p soroban-custom-types-contract
Success!
Solving the Quest
Now, we can try to solve the quest by creating the arguments to invoke each function on the implementation of our Soroban contract.
Deploying the Contract
Before we start invoking, let's deploy our contract using
cargo build --target wasm32-unknown-unknown --release
soroban deploy --wasm target/wasm32-unknown-unknown/release/soroban_custom_types_contract.wasm
Invoking c_rect
First, we have to call the c_rect function, which has the argument _rect of type Rectangle. If we check out the README.md file, we see that we can
Invoke the
c_rectfunction to create aRectangleusing something like:soroban invoke \ --id <contract-id> \ --fn c_rect \ --arg '{"object":{"map":[{"key":{"symbol":"width"},"val":{"u32":<a-u32-integer>}},{"key":{"symbol":"height"},"val":{"u32":<a-u32-integer>}}]}}'
We can see that we should change the values for the sections marked with <a-u32-integer> to an actual u32 integer, and <contract-id> to our contract ID. You can choose any height or width for your rectangle. After you are happy with your choices, invoke the contract.
soroban invoke \
--id <contract-id> \
--fn c_rect \
--arg '{"object":{"map":[{"key":{"symbol":"width"},"val":{"u32":<a-u32-integer>}},{"key":{"symbol":"height"},"val":{"u32":<a-u32-integer>}}]}}'
Invoking c_animal
After successfully invoking the c_rect function, we now will move on to the c_animal function. From README.md, we can
Invoke the
c_animalfunction to create anAnimalusing something like:soroban invoke \ --id <contract-id> \ --fn c_animal \ --arg '{"object":{"vec":[{"symbol":"<a-relevant-animal-symbol>"}]}}'
Again, we should change the <contract-id> to our contract ID, and <a-relevant-animal-symbol> to an animal enum we have created. Since the Animal struct is a custom type we have created, we should create the corresponding object as a JSON.
Invoking c_user
Now, it's c_user function's turn. Again, checking out README.md, we see that we could
Invoke the
c_userfunction to create aUserusing something like:soroban invoke \ --id <contract-id> \ --fn c_user \ --arg '{"object":{"map":[{"key":{"symbol":"name"},"val":{"object":{"bytes":"<a-hex-encoded-string>"}}},{"key":{"symbol":"age"},"val":{"u32":<a-u32-integer>}},{"key":> {"symbol":"pet"},"val":<an-animal-object>}]}}'
That's quite the prompt! We should find the hexadecimal encoded string of a name, change the <a-u32-integer> to an age, and <an-animal-object> to the JSON representation of an animal object.
Remember that we could use the same Dog object that we used for the c_animal invocation, but feel free to use any animal you'd like.
Invoking c_rgb
That one was the hardest! Now, we can invoke the c_rgb function. Again, if we check out README.md, we will see that we can
Invoke the
c_rgbfunction to create aRGBvalue using something like:soroban invoke \ --id <contract-id> \ --fn c_rgb \ --arg '{"object":{"vec":[{"u32":<a-u32-integer>},{"u32":<a-u32-integer>},{"u32":<a-u32-integer>}]}}'
We again have to change the <a-u32-integer> values to any integer values that we'd like.
Invoking c_color
Keep going! Now, we can try invoking the c_color function. The README.md file states that we should
Invoke the
c_colorfunction to create aColorusing something like:soroban invoke \ --id <contract-id> \ --fn c_color \ --arg '{"object":{"vec":[{"symbol":"RGB"},<a-rgb-object>]}}'
where <a-rgb-object> should be a JSON object representation of the RGB struct. Try to construct the object without looking below!
Invoking c_part
You are close! Now it's time to call the c_part function. Let's check out the README.md file once more.
Invoke the
c_partfunction to create an accountParticipantusing something like:soroban invoke \ --id <contract-id> \ --fn c_part \ --arg '{"object":{"vec":[{"symbol":"Account"},{"object":{"accountId":{"publicKeyTypeEd25519":"<hex-encoded-account-id>"}}}]}}'Also invoke the
c_partfunction to create a contractParticipantusing something like:soroban invoke \ --id <contract-id> \ --fn c_part \ --arg '{"object":{"vec":[{"symbol":"Contract"},{"object":{"bytes":"<contract-id>"}}]}}'
Do you remember how to obtain your hexadecimal encoded public key? (Hint: using Python!). Try to find out what to put into <hex-encoded-account-id>. The <contract-id> parameter is pretty simple, as we have been using that one for all function invocations that we have done.
Invoking c_card
Last one! You should be pretty tired at this point, but bear with me. Luckily, the last invocation is quite simple, as we only have to
Invoke the
c_cardfunction using something like:soroban invoke \ --id <contract-id> \ --fn c_foo \ --arg '{"u32":<a-u32-integer>}'
Remember that the u32 argument should be one of 11, 12, or 13, as we only defined those values. And c_foo should be c_card, if that wasn't clear.
verifying Our Implementation
That was a lot of invocations! Finally, we must invoke the verify function of the verification contract 40d12b03a08f5dde4e0068aa752fa65eddf905e82a18f522efe350e0cd268b8a to make the verification contract verify the correctness of our deployed contract. Can you guess which argument we should supply? If you guessed the contract ID of the contract we have deployed, you are right! We can use
soroban invoke \
--id 40d12b03a08f5dde4e0068aa752fa65eddf905e82a18f522efe350e0cd268b8a \
--fn verify \
--arg [your-contract-id]
We should get a reply indicating that all of our custom implementations have succeeded, and none of them have failed.
Nicely done! You have definitely earned your reward! Use sq check 5 to claim your reward 🎉!










Top comments (1)
Hope you enjoyed this post!
If you want to support my work, you can send me some XLM at
GB2M73QXDA7NTXEAQ4T2EGBE7IYMZVUX4GZEOFQKRWJEMC2AVHRYL55VThanks for all your support!