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
struct
s are used for creating objects that have multiple different values. These values can consist of basic types likeu32
andBytes
, anotherstruct
s,enum
s,array
s, and so on. Thestruct
s allow us to carry all properties of an object together on a single variable. Well, it is useful to pass aCar
struct to ainsert_to_garage
function instead oftires
,engine
,color
,fuel_type
,license_plate
,total_kilometers
...The
enum
s 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 whoBob
is. The compiler is not interested in those names, and will happily use their numeric values in your code.
We have a lot of empty struct
s and enum
s, 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
Rectangle
type must be astruct
, with two fields:width
andheight
which both must be au32
value.
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
Animal
type must be anenum
, with at least two variations:Cat
andDog
.
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
User
type must be astruct
withname
,age
, andpet
fields, corresponding toBytes
,u32
, andAnimal
values, 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
RGB
type must be a tuplestruct
type made with a tuple of 3u32
values.
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
Color
type will combine theRGB
custom type nested within a tupleenum
type. Construct yourRGB
struct type as described bove, Then, yourColor
enum type must be defined as a variant with a name of "RGB" and an instance of yourRGB
type.
An enum
s type definitions can be of any type, including custom struct
s.
pub enum Color {
RGB(RGB)
}
Implementing Participant
Almost done! We now need to implement the Participant
enum
type. The definition is
The
Participant
type must be anenum
with single-value tuple variants as follows:
- An "Account" variant with an
AccountId
type- 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 enum
s, 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
RoyalCard
type must be anenum
containing threeu32
integer 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 struct
s and enum
s 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_rect
function to create aRectangle
using 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_animal
function to create anAnimal
using 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_user
function to create aUser
using 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_rgb
function to create aRGB
value 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_color
function to create aColor
using 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_part
function to create an accountParticipant
using 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_part
function to create a contractParticipant
using 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_card
function 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.
verify
ing 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
GB2M73QXDA7NTXEAQ4T2EGBE7IYMZVUX4GZEOFQKRWJEMC2AVHRYL55V
Thanks for all your support!