Exercise: track the longest greeting ever set#
← Back to Week 4: Track exposure
Same rules as 01-extend-the-contract.md: short, optional, no answer key, done on a copy of ../project/contracts/Greeter.vy or directly on it. Exercise 1 in that file has you add a counter that increments on every single call, no matter what. This one adds a state variable that only updates sometimes, depending on a comparison against the current stored value, a genuinely different (and genuinely more common, in real contracts) pattern than "always add one."
The task#
Add a state variable that remembers the length of the longest greeting ever set, and only updates it, and only emits an event about it, when a new greeting actually beats the current record.
longest_length: public(uint256)
event NewLongestGreeting:
length: uint256
new_greeting: String[280]
Add both near the top of the contract, alongside the existing state variable and event. Then, inside set_greeting, after the existing log GreetingChanged(...) line, add:
new_length: uint256 = len(new_greeting)
if new_length > self.longest_length:
self.longest_length = new_length
log NewLongestGreeting(length=new_length, new_greeting=new_greeting)
Read that if for what it's actually doing: most calls to set_greeting should not change longest_length or emit the new event at all, only the calls that set something longer than anything seen before. That guard matters for a real reason beyond just correctness: writing to contract storage costs gas every single time, whether or not the new value is actually different or interesting. A contract that unconditionally overwrote longest_length on every call, even with a smaller value, would be spending gas to record information nobody asked for. Checking first, and only writing when the check passes, is a small, real habit worth having from here on, not just busywork specific to this one exercise.
Notice too that new_length (no self.) is a local, temporary value that disappears the moment this function call ends, while self.longest_length (always with self.) is the one that actually costs gas to write and sticks around forever. That distinction, what has self. in front of it and what doesn't, is the same discipline every state-changing line in this contract already follows; by now it's worth having fully automatic.
Recompile and redeploy#
ape compile
ape run deploy --network ethereum:sepolia:<your-rpc-url>
ape run get_abi --network ethereum:sepolia:<your-rpc-url>
Update CONTRACT_ADDRESS in your Python script.
Prove the guard actually guards#
This is the part that matters. Call set_greeting several times in a row, in this specific order, checking greeter.functions.longest_length().call() after each one:
- A short greeting, like
"Hi"(2 characters).longest_lengthshould become2. - A longer one, like
"Hello there, friend!"(21 characters).longest_lengthshould become21. - A short one again, like
"Hey"(3 characters).longest_lengthshould stay at21, not drop to3. - Something longer than step 2, to confirm the record can still go up again when it's actually beaten.
Step 3 is the one worth not skipping: it's the entire point of the exercise, and it's exactly the kind of case that's easy to get backwards by accident (a < where you meant >, or updating longest_length unconditionally and only guarding the event) and still look right if you only ever test with increasing lengths. If you added the second-event exercise's process_receipt technique already, use it here too, watching for NewLongestGreeting to confirm it only actually fires on steps 2 and 4, never on step 3.