Tutorial Completion

A malicious user can wrap the module that calls the randomness API (let's call it module A) in their module (let's call it module B). In module B, the user checks the result of the function call to module A. If the result is not what the user wants, the user aborts the transaction and keeps trying until the result is desired.

Using the Aptogotchi code from the last section as an example, the user can wrap the create_aptogotchi function in their module, and keep trying until the created Aptogotchi is what the user wants.

module malicious::main {
entry fun abort_if_dont_like_minted_aptogotchi(
user: &signer,
desired_body: u8,
desired_ear: u8,
desired_face: u8,
) {
let [body, ear, face] = @aptogotchi::main::get_aptogotchi(user);
if (body != desired_body || ear != desired_ear || face != desired_face) {

However, in practice, Aptos makes this attack impossible by forcing the randomness API to only be called as a top-level call from a transaction, meaning create_aptogotchi must be called directly from a transaction, so the malicious user cannot wrap it in their own module. You will get a compile error if you try to make create_aptogotchi public.

module aptogotchi::main{
// Mint an Aptogotchi NFT.
// If it's non public like this, it can only be called from a transaction.
// If it's public,
// it can be wrapped in another module and called from the wrapped module.
entry fun create_aptogotchi(
user: &signer,
) acquires CollectionCapability, MintAptogotchiEvents {
// ...