Here are today’s trends to watch from Curve Market Cap:
This Sunday we highlight an interesting bug we ran across when trying to program against the sETH pool, one of the very popular pools given its role in Curve’s exploding cross-asset transactions (over $100M to date).
We were working on building a VolumeGauge to better measure and increase volume to Curve. These contracts are designed simply to interact with the Curve pools, so they basically build right off each pool contract. We’d knocked out a half dozen, and all of them had typically worked without any issue. Yet the sETH pool alone was the first to give us issues with our default implementation.
First off, it would always fail when we tried to exchange sETH to ETH, never the opposite direction. It would also work fine when we would try to run the sequence directly from an address, but it would always fail when deployed as a smart contract. Weird, right?
We asked the helpful #dev channel in the active Curve Discord group, and they quickly figured out the solution. Specifically within the sETH pool, they have this code from lines 448-459 :
if _coin == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE:
raw_call(msg.sender, b"", value=dy)
else:
_response: Bytes[32] = raw_call(
_coin,
concat(
method_id("transfer(address,uint256)"),
convert(msg.sender, bytes32),
convert(dy, bytes32),
),
max_outsize=32,
) # dev: failed transfer
Eureka! This easily explains why it only fails with ETH. Curve pools usually work with ERC-20 tokens, which always have a built-in `transfer
` method. Raw ETH doesn’t have this logic, so the code includes logic to work differently if the coin is ETH, as designated by its vanity hash consisting of only the letter e.
We’re only partly to our solution though, we’ve figured out why ETH was being handled differently than its tokenized sETH counterpart. But why was our contract choking on the raw_call(msg.sender, b"", value=dy)
attempt to wire raw ETH to our contract, but not if we invoked it directly from our address?
In this case, we’d never gotten around to creating a fallback function in our contract. It’s usually good practice within smart contract development to create a fallback function, in case somebody accidentally wires ETH to you without calling a specific deposit function correctly. I mean, you want to make it as easy as possible to get paid, right?
For Solidity before 0.6.0 this was done by creating an empty function name and marking it payable. After 0.6.0 this was split into receive() and fallback() destinations to better distinguish the two most common use cases of fallback functions. Note that fallback function can only count on having 2300 gas to work with, so it’s not going to be able to do much with it except maybe basic logging.
The moral of the story… when in doubt, stop by and chat with the helpful Curve team, who always want to make it as easy as possible to interact with the site.
For more info, check our live market data at https://curvemarketcap.com/ or our subscribe to our daily newsletter at https://curve.substack.com/. Nothing in our newsletter can be construed as financial advice.