Opening ramblings
Composability is a hot word in the crypto/blockchain industry, generally expressing the ability for protocols to seamlessly and permissionlessly interact with each other.
While there may be many reasons for a protocol to adhere to this concept, they likely boil down to:
Ideology: building a protocol/contract/program and deploying it to a blockchain generally means you align to some extent with web3 ethos, and are looking to make your work usable by anyone, to let anyone compose over your protocol.
Business: in most cases, protocols have:
a direct economic incentive to enable anyone to compose over them. Think of an AMM preventing anyone but their own frontend from interacting with their pools (for instance by co-signing all transactions on a private server). They would effectively deprive themselves of the volume aggregators or any higher-level protocols would bring.
a less direct incentive in the form of a virtuous cycle at the ecosystem level, where the existence of modular protocols of different granularities can reduce the effort/time involved in building new and complex products, extend the chain’s offer, bring in more volume, and in turn have it trickle down to those composable protocols.
Ensuring composability in Sui
Now to the heart of the matter: what makes a Sui contract composable or not in practice?
The critical aspect to look out for is side-effects from the perspective of the integrator contract. Strictly speaking, this could be defined as “any action performed in the contract function, that is not directly observable by the calling party”. However this could be seen to encompass actions like event emissions, which are inconsequential to contract execution, so we’ll narrow the definition to “any state-mutating action performed in the contract function, that is not directly observable by the calling party”.
Depending on which other chain(s) you are familiar with, this might strike you as odd. It at least does coming from Solana, where the runtime design enforces that any account address (~ Sui object ID) used in a transaction be passed as input, even accounts about to be created via that instruction. In this context, the only obstacles to composing over a contract are:
gating on the endpoints / whitelist of some sort
obfuscation, e.g. closed-source
The situation is different in Sui however, as it is possible to internally create brand new objects as well as transfer them to a wallet. Let’s illustrate that by building Bad Idea ™️, a protocol that mints a congratulatory NFT for every Sui → Coin trade executed through its underlying trading venues. Here’s what the code might look like:
Now let’s assume that First Amm ™️ - one of the underlying trading venues we are eyeing to compose over - has the following functions (check out this official example for the actual code):
Long story short the dream is over, no lambo. There is no way to call a swap and retrieve the resulting amount from the contract.
But then we hear about Second Amm ™️, a brand new venue with plenty of volume, and the code looks like this:
Extremely small difference. One function made public that performs the swap, but returns the resulting coin object instead of transferring it back to the sender internally. And just like that the dream is back on 🏎️.
Parting words
Those are a lot of words to highlight a simple rule in designing your functions in a Sui contract: never break the chain. It comes naturally to return primitive values from functions (e.g. public fun calculate_swap_sui_output<A>(pool: &Pool<A>, coin: Coin<SUI>): u64
), and it should be the same in the case of objects (public fun swap_sui<A>(pool: &Pool<A>, coin: Coin<SUI>): Coin<A>
) as long as it does not threaten the security of the contract.
This applies to all functions that might be needed as part of a logic chain in integrator contracts, and is extremely important in the case of functions manipulating Coin
s in particular.
These logic chains can and will be complex and hard to foresee, so systematically exposing those composable functions (paired with a non-composable, entry wrapper function when needed like in the example above) is a great way to encourage other protocols to compose over you, and enable new application layers to emerge.
Sui experts Fleetwood Mac said it first: chain keep us together, never break the chain.