In total, we had 33 devs who competed (congrats to WHOA BUDDY who won the grand prize of a limited edition keyboard), and we saw a wide range of answers in the coding challenges. In this post, we want to highlight some devs who went above and beyond in the challenge.
Want to try the challenges before reading the answers? Get started here.
WHOA BUDDY Shows Many Paths to the Same Destination
In the second quiz, we posed a bug challenge to developers that read:
The following code causes an indeterminate error. Resolve the issue inside of <code-rich-text>execute<code-rich-text> by using the <code-rich-text>is-ok<code-rich-text> function.
This challenge was meant to force developers down a specific path for how to solve the problem. However, some developers went above and beyond to showcase different ways the problem could be solved.
One participant, WHOA BUDDY, had a particularly insightful response to this challenge, offering not just one but three different approaches to solving the problem. Each solution demonstrates a unique aspect of Clarity programming and error handling. Here’s what he submitted:
Solution 1
His first solution takes a straightforward approach with an <code-rich-text>execute-v1<code-rich-text> function:
This solution uses <code-rich-text>unwrap-panic<code-rich-text> to extract the value from the <code-rich-text>ok<code-rich-text> response. While simple and concise, it's worth noting that this approach would cause a panic if <code-rich-text>say-hello<code-rich-text> ever returned an error. In this test problem, it is perfectly acceptable to use this approach since we know the return value. But if the response or optional type is unknown, this solution would cause a runtime error.
Solution 2
WHOA BUDDY’s second solution follows the quiz instructions more closely, using an <code-rich-text>execute-v2<code-rich-text> function:
This implementation uses the <code-rich-text>is-ok<code-rich-text> function as requested, checking the response before deciding whether to return the response directly or return a custom error code. This approach offers more control over error handling, which can be useful when you want to implement additional logic that depends on an <code-rich-text>ok<code-rich-text> response.
Solution 3
WHOA BUDDY’s final solution presents a clean and efficient <code-rich-text>execute-v3<code-rich-text> function:
This version uses the <code-rich-text>unwrap!<code-rich-text> function, which combines the benefits of the previous two approaches. It unwraps the <code-rich-text>ok<code-rich-text> value if present, or returns a custom error if not.
These responses not only solved the problem, but also provided valuable context on when each approach might be acceptable - showcasing the flexibility of Clarity and the importance of considering different error-handling strategies in smart contract development.
0xNestor Explores the Art of Protection
In the 6th quiz, we challenged developers to implement a smart contract for a time-locked wallet. One participant, 0xnestor, provided an optimized solution that not only met the requirements but also demonstrated a deep understanding of Clarity's security features.
Let's examine some key aspects of their implementation:
0xNestor optimized the contract by removing the unnecessary <code-rich-text>balance<code-rich-text> variable from the provided template and instead used <code-rich-text>stx-get-balance<code-rich-text> to track the contract's balance directly. This approach is more efficient and ensures that the balance is always up-to-date.
In the <code-rich-text>claim<code-rich-text> function, 0xNestor uses <code-rich-text>tx-sender<code-rich-text> on line 7 to verify the beneficiary's identity. This is a safe implementation because the function is protected by post-conditions on line 11. Post-conditions serve as a crucial safety mechanism in this function. They allow users to set predefined conditions that must be met for the transaction to execute, protecting against unexpected asset transfers without requiring specific knowledge of the smart contract's code.
In this case, post-conditions ensure that only the intended beneficiary can successfully claim the assets, adding an extra layer of security to the claim function.
However, the next function <code-rich-text>bestow<code-rich-text> is not protected by post-conditions (since there are no asset transfers. Instead, this functionuses <code-rich-text>contract-caller<code-rich-text> for authorization. Using <code-rich-text>contract-caller<code-rich-text> ensures that even if this function is called by another contract on behalf of the user, the <code-rich-text>is-eq<code-rich-text> check would fail since it was no longer the beneficiary who called the function directly. If <code-rich-text>tx-sender<code-rich-text> were used in this scenario, it would be possible for another contract to act as beneficiary to behave in unexpected ways.
Codetagonist Uncovers a Subtle Security Flaw
In the final quiz of our Master of Clarity challenge, we tasked developers with identifying and exploiting a vulnerability in a smart contract, then proposing a fix. Codetagonist's submission stood out for its ingenuity and thorough approach to both exploiting and securing the contract.
The Vulnerability
Codetagonist identified a subtle but critical vulnerability in the original contract's <code-rich-text>change-owner<code-rich-text> function:
As touched on earlier, the vulnerability lies in the use of <code-rich-text>tx-sender<code-rich-text> for this particular authorization. While this seems secure at first glance, it can be exploited by a malicious contract that calls this function in the context of the current owner. Let’s take a look at what that might look like.
The Exploit
Codetagonist's exploit is particularly clever because it's disguised as an innocent NFT minting contract. Here's the key part of their malicious contract:
This exploit works by:
- Offering a seemingly innocent mint function for an NFT.
- Hiding the malicious code in a private <code-rich-text>check-minter<code-rich-text> function.
- When the target (the current owner of the vulnerable contract) mints an NFT, it triggers the change-owner function of the vulnerable contract.
- Because this call is made in the context of the current owner (<code-rich-text>tx-sender<code-rich-text>), it passes the authorization check and changes the owner to the attacker's address.
The Fix
Codetagonist's proposed fix addresses the root cause of the vulnerability:
The key change is the use of <code-rich-text>contract-caller<code-rich-text> instead of <code-rich-text>tx-sender<code-rich-text> in the <code-rich-text>is-owner<code-rich-text> check. This ensures that the actual caller of the function (not just the transaction sender) is the contract owner, preventing the type of exploit demonstrated in the malicious contract.
The Tricky Truth About SIP-010 Token Transfers
We had a wide range of challenge participants, ranging from new Clarity developers to seasoned veterans, but one question around fungible tokens stumped the majority of developers, regardless of their level of experience.
In Quiz 7, we asked developers a straight-forward true or false question:
True or False: In a SIP-010 compliant fungible token contract, the transfer function can be called by any <code-rich-text>principal<code-rich-text> to transfer tokens they do not own.
Most devs replied <code-rich-text>False<code-rich-text>, but in fact this statement is <code-rich-text>True<code-rich-text>. Let’s dive into an example that demonstrates the distinction between what’s technically possible and what’s considered a typical best practice.
The Proof Is in the Code
Let's walk through a minimal SIP-010 compliant contract that allows this behavior:
The <code-rich-text>transfer<code-rich-text> function in line 3 is the key to this behavior. Notice that it doesn't include any checks to verify that the <code-rich-text>contract-caller<code-rich-text> is the same as the <code-rich-text>sender<code-rich-text>.
The reason this works is that the <code-rich-text>transfer<code-rich-text> function uses <code-rich-text>as-contract<code-rich-text> to perform the transfer. This means the contract itself is the one executing the transfer, not the caller. Since the contract owns the tokens (assuming it has tokens), the caller of the function (you) is allowed to transfer the contracts tokens.
And since the SIP-010 standard doesn't dictate the internal implementation of the function, only its interface, the answer to this question is <code-rich-text>true<code-rich-text>.
The Truth About contract-call? Costs
Another question that stumped a few of our developers, regardless of their experience level, was:
True or False: The <code-rich-text>contract-call?<code-rich-text> function has a fixed cost regardless of the complexity of the called function.
The correct answer in this case is True. Let's unpack why this one was a bit tricky.
The Fixed Cost of contract-call?
If we look at the cost functions in the <code-rich-text>cost-3.clar<code-rich-text> smart contract, we’ll see the following for <code-rich-text>contract-call?<code-rich-text>:
This shows that the <code-rich-text>contract-call?<code-rich-text> function itself has a fixed runtime cost of 134 units, regardless of what function it's calling or how complex that function is.
When you measure the cost of a contract call in practice, you'll see widely varying costs depending on what function you're calling. But that’s because the function being called can vary widely in its cost. As one seasoned developer put it:
"In general contract-call? as a function has fixed costs. But you don't see it when you measure it. The most significant portion of the cost is from the function you called, not contract-call? itself.”
When optimizing for cost, it's crucial to focus on the efficiency of the functions you're writing, as they will typically account for the majority of the runtime costs. The fixed cost of <code-rich-text>contract-call?<code-rich-text> is generally negligible in comparison.
Thanks for Competing
Shoutout to all of the devs who participated in the challenge and a special salute to WHOA BUDDY, 0xNestor, and Codetagonist for going above and beyond in their answers. We hope you enjoyed the challenges and competing with other devs.
If you want to continue your journey into Clarity, check out our brand new docs covering Clarity functions and chat with our team in the Hiro channels on Discord.
When you're ready, test your knowledge by taking the interactive Clarity quizzes yourself.