New Ethereum talks, every Monday. The week's conference uploads by event, in your inbox.

Loading player…

Make Any DeFi App Atomically Composable | Viraz Malhotra - Shell Protocol

Ethereum DenverWed, Oct 9, 2024, 12:00 AM

The aim of this workshop is to make any EVM dapp universally composable with other dapps via a smart contract adapter to a defi connector hub. Using Foundry, participants will build their own contracts and design a fork test for it. Then we’ll walk through the flow of an example transaction APPLICATIONS FOR ETHDenver 2024: #YearOfTheSpork ARE OPEN APPLY NOW https://ethdenver.com/apply

Transcript

Hello everyone. My name is Vira and I am a smart contract engineer at Shell Protocol. Today I will show you how we can make any D5 DAP atomically composable. We're going to talk about the current problems that exist in the D5 ecosystem, and then we'll dive into the code aspect and, you know, explore the solutions we can implement to solve those UX problems we have. To begin, how many of you regularly use D5 DAPs?

Raise your hand. Okay. Yes, this is going to be fun. Well, this image accurately describes the current state of DeFi in general right now , where we can see that we are still a little short of reaching an ideal state of challenge. So we're still a little way from that, and there's a slight difference in terms of how we talk about it and what we actually see in terms of UITS in different D5 applications.

Okay, and I want to say that the ideal vision would look something like this: also, ignore the approval transaction, which is a perpetual headache that we already have, and the second transaction, just focus on that. So that's the ideal state for D5; it should be where multiple complex operations can be performed in a single transaction. For example, you can simply, let's say you have encapsulated BTC, so you can use it and buy an NFT in a single transaction by performing multiple operations, as you see by performing multiple operations, as you see . But the reality is far from this, because the reality is something like this, right?, where you have to carry out several transactions to achieve it and that involve, you know, several approvals, switching between different websites, a lot of high gas costs and, of course, also ME risks, especially with AMM-related operations.

And at Shell we've been working on a sort of solution for this, where we've created a set of smart contracts and differentiated the accounting layer and the business logic layer. Basically, you can have a way to improve the user interface that way, where you don't have to put everything inside one contract, and you can split it into two parts and make it more modular, so you have two different layers. One is the accounting layer, and then you have the business logic layer. And the accounting layer we've developed is called Ocean, which is an ERC15 contract that users can basically interact with. And the business logic, which performs all the calculations, can simply be a black box for users, a black box for users, right?

So, the idea is that users pass an array of struts to a smart contract method that, in a protocol definition, we call interactions. Therefore, interactions are nothing more than a kind of batch operation that users can use to perform these different related primitive operations , for example, swapping , for example, swapping , such as adding or removing liquidity, etc., and we will delve deeper into this as we go along. Okay, this is a short snippet of the user interface we have from our current DAP, where we have a couple of examples. The first example we have is a multi-NF exchange where you can use, where you can exchange multiple NFTs for USDC in a single transaction, right?

And again, this shows how composable your DAP can be, where you can minimize user clicks, you know, and do all the templates in a single transaction, templates in a single transaction, right? And this is an example where you can exchange an NFT for an ERC20 or vice versa. And the other example we have is an LP2LP swap, have is an LP2LP swap, right? For example, with different AMMs right? For example, with different AMMs , right?

Now, if you need to migrate your liquidity from one group to another, you have to make multiple transactions, to make multiple transactions, right? But with shell, you can somehow do all of this in a single transaction because of the composable smart contract architecture I just talked about. Well, with composability, we've realized that, to achieve greater adoption by users and developers, having a composable system gives you the ability to basically integrate with various D5 protocols without much code complexity, right? And what this does is help you create a system where you can basically be a one-stop shop for D5. For example, a retail user venturing into Web3 doesn't need to know anything about various protocols or how different protocols work.

He can simply go to a platform and carry out all his operations right there , and everything that happens behind the scenes can be a black box to him, right? And that reduces complexity and, in a way, simplifies the user experience, which in turn would help us get more users on Web 3 somehow, for use cases related to D5, right? Therefore, composability is directly proportional to these omnidirectional applications where , as I said, you can perform several operations with a single interface instead of having trouble switching tabs, managing different tabs, changing websites, etc., and also greatly reduce gas costs, right? Yes.

So, to do all this, to achieve this vision, we've started working on it and developed a set of smart contracts called adapters, which is simply middleware between shell contracts and any external D5 contract, such as Unis Swap, Balancer, or Curve D5 contract, such as Unis Swap, Balancer, or Curve . The idea is that, behind the scenes, shell contracts communicate with these adapters. And these adapters call these external smart contracts and can therefore compute that result and perform any operation, for example, a swap or adding liquidity, etc., and then simply communicate it back to the shell contracts. That's how the cycle works, right?

Where there is bidirectional communication between the ocean, which is our accounting layer, and the adapter, and then there is unidirectional communication between the adapter and the external contracts. This is how the entire user flow works, which we'll delve into more as we go along, right? Well, this is just a simplified version of the diagram we talked about earlier, where there's a user calling the ocean, a user calling the ocean, right? And there's a method called compute for performing interactions, right? So, there's a method in the ocean contract called interactions, which is the one that establishes a matrix of stcts, right?

And that strct is called interaction, right? As I mentioned before. So, you pass a matrix of different interactions and the interactions can basically be...the user flow starts first with a user locking their underlying token. It could be an NFT.

It can be any ERC20. Then they lock that ERC20 token and obtain an ERC15 token in exchange for the same value. TRUE? And once they do that, then they move on to the second interaction, which is nothing more than the business logic-based interaction where they can, you know, say, add liquidity or swap tokens, right? And then, finally, once that is the intermediate interaction, the last one would be to unwrap the token.

For example, if I want to exchange USDC for USDT, I will first wrap USDC. The second interaction will be to exchange the shell-wrapped USDC token for a shell-wrapped USDT token, which are again the ERC15 tokens I talked about. And the third interaction would be to unwrap the USDC token in shell for the underlying USDC token, which is the one you get right at the end. So basically you can start with one token and retrieve the other token with just one click. That's the idea.

And now I'll dive into the codebase. So, if you have your laptop, you can clone this repository and we can... Anyway, I'll show you this presentation, dive into the code, and if you want to follow along better, you can clone this repository and, you know, just dive in with me. Yes. Yes.

So now I'll dive into the code. Yes. Yes. I hope this screen is visible to everyone. Or I should...

I visible to everyone. Or I should... I probably will... Yes. Okay, so the idea is that this is basically what an interaction looks like, and let me know if you need me to show the GitHub link at the end.

Basically GitHub link at the end. Basically , they can go to...they can search for continuous integration labs on GitHub and they'll find a strange GitHub link and the first repository they see is called E Denver Adapter Workshop. So they can clone it. This is the link.

I'll leave this link open for a while so people can clone the repository if needed. Yes. So yes. His name is Trowy. The name of the GitHub organization is Trowy and you can go there and the first public repository you'll find would be E Denver adapter workshop and you can clone it and there are steps in the Readme file where you can first run yan and then, I'll probably go over it once.

Yeah probably go over it once. Yeah . So you can clone the repository and then you can, first, install all the dependencies with `yan install`, and you can simply run `forgeb` to install the Foundry-related dependencies , and that's it, and I'll dive into the code and we can go over the testing and the code side of things now, up to this point. I just wanted to consult with everyone up to this point; Do you have any questions we should go over before diving into the code? Because it's a workshop, so I'm just making sure that, well, well, then I'll dive into the code.

Yes, yes. So this is basically the starting point, where the interaction strut I was talking about is, right? And the first thing we have is a byte parameter in the strut that we pass to the ocean, which is called interaction type and direction. So this is just a coded form of the interaction type, which is an enumeration, and the address of the external business logic contract you want to interact with, right? Or the ERC20 token, in case you want to encapsulate or decapsulate, is the address of the underlying token you want to encapsulate or decapsulate, or in case you want to do a business logic operation , the address here is the address of the business logic contract , right?

And the inter and these are the types of interaction you can use, right? So, we have an ERC20 token wrapper, a double ERC15 token wrapper, and 721 tokens, right? So, the ocean is compatible with all these types of tokens. So, those are the wrapped transaction types, and then the unwrapping types are again the same, and then there are the calculation output quantity types and calculation input quantity types , which are basically the interaction types related to business logic. So, the calculation output is basically that you have an input token that you are passing into the ocean and you will get a specific amount of total output depending on the value of the token you are providing.

TRUE? And then it's just a buy and sell operation in a way. Correct buy and sell operation in a way. Correct , and now, we will delve deeper into what is the method we call in the ocean. Correct.

Above. Yes. I'll need to... Correct. Above.

Yes. I'll need to... Yes, I'll just go deeper. Yes. So this is the smart contract method that any user requests in the user interface.

For example, if you interact with the shell application, which is called performing multiple interactions. As you can see, this indicates an interaction matrix of the interaction structure, and then it has identifiers, which are basically the ERC15 token identifiers that we use to calculate, take, and keep track of the accounting balance, posting the ERC15 totalizer balances in the smart contract, right? And then the third part is, of course, the address of the user who interacts with the method and who will receive the tokens in the end, right? And then what happens is that this contract will basically call the business logic contract depending on the address you pass in the interaction type parameter and byte address that I mentioned, which is this. Basically, we decode this value you pass to this argument and, based on that, the address we get is a...

Basically, we trace the primitive, and the primitive we trace the primitive, and the primitive , which is the adapter in this case, calls the external contract to communicate and perform the operation the user wants to perform, right the user wants to perform, right ? Here we have the address of the adapter I was talking about, which is called an ocean adapter. It is an abstract contract that is basically inherited by the various adapters. The idea is that you call the multiple interaction method of computation in the ocean and the ocean calls the output quantity method of computation that you see here, where you see that two operations, three operations occur. The first reason is that in order to deposit the underlying token into external contracts, we will first have to unwrap the token.

Because, as I said, when you interact with the ocean at the beginning, you'll have to wrap your underlying token in a shell, based on u, in the shell, ERC15 token, right? So, once the token is wrapped, you'll first have to unwrap it. This is what we 're doing here. First we unwrap the token and then we call this primitive computation output method , which basically calls the adapter and calls the adapter- specific external contract method . And this method, this method, basically communicates with the external contract to perform a particular operation, for example, swapping or adding liquidity, etc.

And then you get a result from this method, which is the output amount, correct, basically. And then you basically wrap the outgoing amount back in the ERC115 form, right? And that's where, that's how you can, you know, basically wrap, you know, you start with one total and end with another. So this is the whole process, uh, in terms of interacting with the external contract. Now I'm going to dive into this primitive exit method , right?

So here's an example we recently submitted, which is a Curve two-pool adapter , where, uh, uh, uh, we're interacting with the Curve pool that has two tokens, right? And in this case, uh, in this example, I'll take an example from a USDC/USDT group, example from a USDC/USDT group, right? So, uh, yes, so this is the smart contract method I was talking about, uh was talking about, uh , in the link above, right? This is the method that has been called, where, and as you can see what happens here now is that the first operation is, uh, where we convert the decimals, right? So, the idea is that, in ocean words, when we wrap, application totals, the wrapped totals are in an 18-decimal format, right?

And now, when you unwrap it, of course, we'll have to convert the decimal format to the underlying total decimal. For example, if you're working with USDC, sure, and the shell toten, now the shell version of the USDC toten, has 18 decimal places, but USDC, the underlying toten, only has six decimal places, so you need to convert those values ​​in terms of the decimal of that particular toten, right? This is what happens here. We get the raw input/output amount and then what we do is, since an AMM can have three operations, it can be an exchange, it can be a deposit or a withdrawal. So, here we determine what type of trumpet it is, right?

And once we do that, we basically rely on the type of trumpet we call in the 12 smart contract methods . So, it could be an exchange, or it could be, you know, a user who wants to add liquidity or remove their liquidity from the pool, right? This is how it works. First, we convert the total decimal places , then we determine what type of calculation it is, and then we call the external smart contract method. And in the end, we get the output amount and, again, we convert the decimals into the specific ocean decimal amount, which is 18 decimal places, right?

In case the total output is less than 18 decimal places, right? So we convert it back to 18 decimal places and return this output amount. That 's all, right? And then, as I said, you simply wrap this total said, you simply wrap this total , right? Okay, now what I'm going to do is...

I'm going to...yes, I'm going to guide you through a test, okay? Give me a second. Does anyone have any questions at this point? We can review it and I will proceed accordingly.

OK. Don't worry. Yes. Yes. So I'll show you what a kind of test looks like in terms of implementation and testing, like testing this in a real-world environment.

We have these four test suites that we've created to test these adapters where we can fork the real environment in this case because our contracts are implemented in Arbitum. So, we used Foundry for the arbitum network, we used Foundry for the arbitum network, right? And basically, we run all these smart contract tests for these adapters, for these adapters, right? And in this case, as I said, the example we're using is USDC and USDT, right? So, we have like three fuzz-based test scenarios , right?

So, we're starting with an exchange, right? I'll probably have to ... What's wrong with this? Okay, yes, so... We'll start with a trade, a test trade, right?

Where we have taken a boolean argument in terms of which token we want to exchange and the amount we want to exchange, right? And based on that, we establish the entry and exit directions accordingly, right? And now, in fact, I had a task here because I wanted us to tidy up the test scenario code, but I can probably rewrite it and go over the interactions that way. Yes, I probably will because, well, not many people carry their laptops with them. So it's probably better for me to just write, retrieve the code, and then go through the test code.

Please bear with me for a couple of minutes. Oh I'm sorry. I'm doing it for a couple of minutes. I'm just returning the code. [ __ ].

Yes. Thanks for putting up with me, guys. Yes. Yes. I added the missing code and now we'll dive into the testing.

Good. As I said, this is where we start by creating these interactions and then calling the Ocean smart contract, for example, to swap USDC or USDT for the other token with the help of... The idea is to have an array of three-element interactions, and the first element, as I said, is basically an ERC20 wrapper interaction. So, first we wrap the token we have, which is either USDC or USDT, depending on the first input we get. So we do this, uh, and we, uh, and as you can see, we can uh, and we, uh, and as you can see, we can , uh, we have a property here called specified quantity, where we pass the quantity, u, of the totals that we want to, uh, swap, that we want to, uh, swap, right?

And, uh, metadata, we simply, uh, we don't need any metadata here, uh, because it's just a wrapper operation. So we just do this, right? And uh, the other do this, right? And uh, the other ... then the second interaction is basically the calculation interaction where, uh, the calculation output quantity interaction where, uh calculation output quantity interaction where, uh , we define the total input we want to exchange and the total output we want to obtain, right?

And the amount we want to exchange And the amount we want to exchange , right? So, uh, as you can see, here's the computation output interaction that we passed to this, uh, to the interaction type and direction argument that we discussed earlier. And this is the address of the adapter I just mentioned, which is the Curve Twool adapter, right? Basically, we concatenate these two values ​​and then create and obtain a result of bytes, 32, correct bytes result of bytes, 32, correct bytes , 32 values, which we pass to the strut. And the input, toten, is again the ERC15 ID encapsulated in shell, and the output total is again the same for the output toten address.

The specified amount was u, so the ocean has a check where if you exceed the maximum amount, it will automatically take the amount from the balance that you are entitled to and want to exchange in the previous interaction. Therefore, it will automatically take the amount we passed, right? And now the final interaction is to decapsulate that token, right? So, once you make the swap, the last operation you do is get the shell-encapsulated ERC20 token, record it, and then return the underlying token to the user, right? And that's all.

And you simply call this a multiple iteration method , and we can see that we have a check here in terms of the decrease and increase of the total inflow and outflow balances of the user who called this method. And we have these checks that the balances should have increased and decreased, and based on that we can identify that we can make these complex operations work in a single transaction, work in a single transaction, right? And the same applies to deposit interactions. The only difference you'll see is that the total outflow is basically the outflow direction is basically the LP direction of Curve in this case, because with Curve when you deposit liquidity you get an LP token back. So this is the address of the LP token that we'll use, and we'll unwrap the LP token at the end, right?

So it's practically the same process as in the exchange. The only difference is the overall direction we're using, right? And the other thing is, which is the last part of this, is the test withdrawal, which is again just the withdrawal side of things where again the difference is that the entry token is the LP token and the exit token is the out, uh, it's USDC or USDT, depending again on the first entry we get, right? So we can base it on that, we'll have, uh, we have like the intelligent test logic in a managed way, in a way where it will adjust the direction of the total output. Um, correct, and again the uh, the interaction process, again, is the same.

You have, you know, a three- element array, and uh, the input address in this case is the LP token, and the output address is, as I said, based on the first inputs, based on the first inputs, right? And again, we verify the balances at the end, which should be correct, right? And now I'm going to run it in my local terminal to show you what the transaction values ​​look like. Yes. Yes.

It will probably just increase this. Yes. Yes. So, it's just compiling and running the three test scenarios we just discussed, and it will show a lot because with Foundry you can see the test logs, you can see the test logs, right? It's just taking a while to compile.

Oh, give me a second. I'm sorry . Yes, it's just taking a little while to compile, and then once we have it, we'll be able to see the test logs. Yes, just a few warnings and yes, it's just running the test. Okay, let me check this once.

There's probably something wrong with it. I'll probably use it once. Yes, I'll probably debug it. Yes. Anyway, the idea is to do these operations basically in an atomic way, in the sense that you can do several operations, several interactions and calculate them to combine them into one.

And yes, if any of them...and we love it...we're looking for more developers to develop more about Shell, such as creating their own adapters that interact with these external D5 protocols . And our team will be here on site all week. So, if you have any questions or want to discuss any ideas, we'd be happy to talk about it.

Well, and we'd love for more people to experiment with shell contracts. They have been audited and a new version has recently been released . And we've released a new version, v3. And yes, we'd be happy to chat about contract architecture or any use case that excites you in terms of development for the hatathon or in general. And this is our website.

You can scan the code and get more information about it . And yes, I'm happy to answer any questions. Yes, thanks. Does any questions. Yes, thanks.

Does anyone have any questions or...? Thank you.

Automatic transcript — names and jargon may be misspelled.