There has been much debate about the current convention for external function naming. Much of that debate has happened earlier in the ecosystem’s development to a smaller audience, so, at the risk of beating a dead horse, I wanted to open the discussion more broadly here.
Starknet and Cairo have made it an explicit non-goal to provide evm compatibility, opting instead to design a platform around the unique constraints of zkstarks. An existing convention has formed to maintain a façade of compatibility through contract interface function naming. In the following, I’ll make a case that this convention doesn’t accomplish its goals, as well as propose some potential paths forward.
Through the EIP process, the Ethereum ecosystem, on top of which the Starknet blockchain operates, has standardized interfaces (ERC20, ERC721, ect) based on Solidity implementations. Solidity uses camelCase naming by convention. Cairo uses camel_case. As such, we’ve landed in the awkward spot where Solidity conventions are leaking into Cairo implementation, in an attempt to make interfaces compatible.
While the goal of compatibility is a reasonable one, function naming is just a human readable representation of a contracts interface. Ultimately, the compiled artifact is the interface for compatibility and Solidity and Cairo differ significantly in how function names are mapped to entry point selectors by the compiler.
Solidity supports argument dependent lookups, hashes function names with keccak, and uses the first four bytes as the selector:
keccak("helloWorld(uint256 foo)")[:4]
Cairo does not support argument dependent lookups, hashes function names with keccak, and uses the first 250bits as the selector:
keccak("helloWorld") & 2^250-1
As a result, while function interface naming is the same, the lower level interpretation is different, and thus there isn’t a de facto “compatibility” as a result of sharing function naming conventions: any existing libraries would need to introduce some level of adaptation to support Starknet.
Nevertheless, it seems there is enough support for maintaining camelCase external interfaces.
Potential conventions
As far as I see it, these are the possible paths:
-
Maintain the status quo and use camelCase function naming conventions for external interfaces, as suggested in the Open Zeppelin extensibility pattern.
Pros:
- Naming is familiar to existing ethereum ecosystem developers.
Cons:
- Complicates Cairo conventions.
- Introduces mixed casing to code bases.
- Creates an illusion of compatibility.
-
Use snake_case function naming conventions for external interfaces, as is standard with Cario code.
Pros:
- Simple, easy to understand convention.
- Aligns with Cario ecosystem.
- Interface compatibility explicitly a non-goal.
Cons:
- Naming is different from ERC counterparts.
-
A compromise, entry point normalization: Normalize entry points before hashing, making them case insensitive and removing underscores:
keccak(lower(replace(function_name, "_", ""))) & 2^250-1
Pros:
- Casing doesn’t affect entrypoint calculations.
- Cario code can use snake_case and other libraries can continue to use camelCase.
- Disambiguates public interface naming, making it less susceptible to spoofing.
Cons:
- Complicates entrypoint calculation.
- Backward incompatible.
My perspective
Personally, I have not seen a compelling case where maintaining ERC convention function naming results in compatibility / interoperability and as such, do not think it is worth the added complexity and prefer approach [2]. In fact, it might well accomplish the opposite of its desired goal, since one would reasonably assume that an ecosystem that adopted such an unconventional behavior would result in some level of compatibility.
That said, I’m open to examples of where this convention results in compatibility / interoperability and willing to change my mind. In particular, I believe the bar for compatibility / interoperability should be that existing tooling / infrastructure can interoperate with Starknet without any Starknet domain knowledge. If it ultimately will always require some level of adaptation, then my preference would be for external libraries to adapt the Starknet interface to the Ethereum counterpart, i.e. Typechain could maintain a mapping for ERC standard methods with corresponding entry point selectors. That way we can isolate the logic necessary for interoperability rather than spread the overhead to the entire ecosystem.
If there are compelling use cases for maintaining this convention, my preference would be to normalize entry point selector computation [3] and make it agnostic to casing, such that existing Ethereum tooling / infrastructure can use camelCasing which would map to the same entry point as the camel_case representation.