Check out a free preview of the full A Tour of Web 3: Ethereum & Smart Contracts with Solidity course

The "Diamond Contract Walkthrough" Lesson is part of the full, A Tour of Web 3: Ethereum & Smart Contracts with Solidity course featured in this preview video. Here's what you'd learn in this lesson:

ThePrimeagen clones a repository containing a diamond pattern template and walks through a more complex use case. Within the diamond pattern, a facet refers to a contract within the diamond. When a facet is cut, it is either added, removed, or changed. An init method is used to initialize a contract with the diamond's storage location rather than the individual contracts storage location.


Transcript from the "Diamond Contract Walkthrough" Lesson

>> There we go, all right, so I think that's enough about storage, so let's close that. And let's see, we have a we have already done that, now this makes sense, I remember that I showed you the app storage technique. So I think hopefully, everything we've done now really makes the diamond pattern easy to consume.

You understand there's a fallback, this fallback can be used to Lookup Functions, these functions exist in separate contracts. We're going to delegate call out to that contract, so we use our own storage. We've all agreed to look up our storage, with this special magic way to look up storage for everyone's contract.

We'll never collide, we don't have to worry, when we return out the results, all the magic will happen through the diamond in return out. We can use interfaces and ethers to do get contractAt, here's what I think the shape should look like. Here's my diamond address, so we can constantly change our diamond into the shape that we want it to be.

Because we already know, we've uploaded those functions there, there's also other things you can do such as, it's called the diamond lobe. Which is a play on words because a lobe, I think is the thing you inspect the diamond with, like I said, a lot of bespoke terminology there.

There's effectively discovery, you can call into a diamond and say, what are your functions and it will print back to you, their functions. So, discovery is built into it, they call it a lobe, all right ,so let's go to, I call this part Lucy in the Sky with Diamonds, very excited.

Remember when we talked about great groovy upgrade, I actually called it because I won the largest initialism ever used at Netflix. When I had the upgrade groovy at Netflix, I called it great groovy upgrade in the Sky with Diamonds. Because I wanted the name, and after the Beatles and all that, and it was just great.

Then I had this acronym that was 17 letters long, and people would ask me what it means, beautiful. All right, so we're going to do a diamond contract, we're actually going to go through the whole process. So I'm just going to program, this you can try to program along with me, it's just there's so many steps, but you've done so many deploys today.

You've done so much of it, you should just be able to kind of naively understand it. But it'll just take too much time to kind of go through it step by step and have everyone keep, up so I'm just going to go quickly. And just experience the vendor for pleasure of watching someone go for a moment.

So I'm going to clone down this, which is called the diamond-3-hardhat. This is that person that I was talking about that made the diamond standard, which is EIP-2535. And he has some associated repositories, where you can actually get a template, for your contract that you want to develop.

So we'll go like this, we'll go to my personal, I'm going to rm-rdf, because I bet you I probably already have something with the word diamond in there, right? Yeah, I just knew I would, so I'm going to go git clone, let's download this thing into my personal folder, jump in here and go nain yarn and install, right.

All right yep, you uninstall so now we just need to run this script, but we need to do something about that script. So I'm going to do a couple things to this library really quickly. So first I want to kind of prove that all these things work, so we kind of all understand them.

So first, I mean, they might by the way, obviously I think the diamond repository might win the contest for most amount of npm modules. So I hope you guys don't feel bad, if you don't feel like I didn't do a good enough job, I really went out here, tried my bestest.

All right perfect, so now that we've done that, I'm going to vim into this and I'm going to do a couple things. So first let's go to contracts, and I'm going to create a simple contract called A.sol. Yes, we're going to rely on this very inconveniently named Function I'm going to do a pragma solidity 0.8.0.

And then I'm going to create a simple contract, contract A, get a function getA, returns 42, up all the wrong keywords in all the wrong places. Public pure returns u, and there we go, so we have a singular function on a contract, right? I just wanted to make it very, very simple because I want to highlight its uses, not show you how complex it can get.

All right, so we have that, fantastic, so next let's go to the deploy script, so let's walk through this together. So, remember the getsigners thing, this is one of the convenient methods, that's added by hardhat. It assumes that hardhat's being executed, it's also in a js file, so, we're just going to allow js to do its thing, which means nothing.

And so we grab the signers, and we grab the first one, okay, we understand that. We're going to grab the DiamondCutFacet, and deploy it, this is the function that is used to add, remove, and replace functions that the diamond uses. So it's its own contract, this is kind of where the 200 IQ move happens, then we deploy the diamond.

Which actually references the DiamondCutFacet, and actually goes and calls into its method. And brings it into the diamond, and then adds that as its only function. So when the diamonds deployed for the first time, it has a single function on it, which is called DiamondCut. Basic terminology is that, each one of your contracts that hang off of diamonds called a facet to add, replace, or remove, they call them cuts.

I know a cut usually reduces, but in this case you add with a cut, it's just very, very positive honestly, if you think about it, so there we go. So let's deploy these two things and let's just kind of cut it right here because we don't want any more of this, return diamond, there we go.

So this deploys, the diamond, it does the cut, plus it does the deploying of the diamond, it returns the diamond object. So we're going to use this as our first step, after that, we need to look at some more code. So this next part might be a little bit confusing, but remember how I talked about, how there's contracts, or there's constructors on your contracts.

Well what's the problem with a constructor, fundamentally when it comes to the diamond pattern? Can anyone try to think about it, with all the things that we've talked about, because I'd love to hear one of you say it.
>> Is it because of baking the arcs into the compiled?

>> Very close, but no. All right I'll tell you, remember how storage is in perspective of the diamond, when does a constructor get called? It gets called at the deployment of the contract, with whom storage, is the storage in perspective of, add constructor time. Let me put it this way, we deploy our diamond, we then deploy contract A, its constructor gets called with A's memory.

The diamonds memory is not involved in deployment, because that's not how any of this stuff works. So what we really need is an INIT function on A, if we wish to have a constructor, that can be called in relation to the diamonds memory. And so they have this whole notion where, whenever you add a cut to the diamond, or when you add replace, remove.

You can also specify an INIT function, that will happen and be called in perspective of the diamond contracts. So that way any constructor logic you have, is actually in an INIT function with the correct perspective, does that make more sense? Hopefully that makes a little bit more sense, I know it's kind of getting iffy at this part, takes a little bit of experience but it makes a lot of sense.

So I'm just going to get rid of that, we don't need that, we're not going to do it. Next, they go through some basic examples of how to do a cut, so I am going to keep this and I'm going to like this. Let's go async function, what should we call this, how about we call this one, addA, that feels good to me.

I'm going to pass him the diamond for A, and there we go, so let's see, we don't need any of this, right? We don't need their specified functions, but let's grab this stuff and put it in, all right. Now look at what they're doing, they were going through each one of the contracts that they previously specified.

They got the contract factory, we're totally familiar with this at this point, they deployed it, we're totally familiar with this at this point. They waited for it to deploy, okay, this just looks like a contract deploy, right? So they deployed their contract out, but then they added the address to the contract, an Add action and this thing called functionSelectors.

Function selectors just go through all of your public functions, hashes them, so creates this four-byte representation of your function, and passes that in. Because when you call a fallback function on the message object, which is that public object that's available on every single function. Call where I can get like, say the sender of this, there's also .sig, which stands for signature.

Which is a four-byte encoding that's identical to what's happening right here. So we can say, I see what's happening in my fallback function, I have found the function signature. And it's associated with this address, so therefore I'm going to call it. Now before I continue on that, it might be a little bit easier to just look at the diamond code.

So I'm going to jump into the diamond code, and go down to the fallback function, so A, they do that exact same thing that we do. They grab out special storage just for their diamonds so they don't accidentally clobber your storage. They then go and do exactly what I thought they would do, go and grab out some location based on the signature.

Kind of what you would expect them to do, then they get the address, now notice that this looks a lot like this facet address, right? Facet address, it is pretty much the same object being stored, so they grab out the facet, it has to not equal zero meaning, this thing has to exist.

If you try to call a function that does not exist, it's going to air with sorry this function does not exist on the diamond, I can't find it. Now remember how I said fallback functions get a little bit confusing, here's the assembly part that I can't write by hand.

I'd have to go look it up in the docs and follow this very closely, but effectively what it's doing is. It's copying all the data from the call into some sort of special space, I don't really understand where it's calling it, but it's doing it. Then it does a delegatecall, which we've talked about plenty of times, it's going to call this function.

In perspective of our own diamond memory, the facet is the address to where it's doing it. And it's going to go from zero to the size of the calldata, then I don't know what these other two zeros are for. Then, on return, it's going to copy in all that data again back into our area and if it was not successful.

We revert the transaction with whatever message was returned to us, else we return with whatever message was returned to us. This mostly makes sense, right I don't think any of us could write this first try, but we certainly can walk through, and understand the general concept of it.

And so that's all the fallback functions doing on the diamond exactly, almost to the tee. What we thought it would be doing based on this drawing right here, everything's coming through the address. It's looking at it up in some sort of array, it's calling it in perspective of the diamond, the return values are coming back out, it's doing it all, awesome.

There's one other place we probably should look, I think I can kind of give a nice little explanation for, let's go right here. So when we do the cut, it's going to go through and it's going to say, hey, is this an ad if it's an ad, we'll add it to our list.

If it's a replace, we're going to replace it from our list, if it's a remove we'll remove it from our list, or else you've done something wrong, right? So after it does all that, which we're not going to really concern ourselves with, it'll omit a nice little hey, we've had a change in our diamond contract.

So you can actually listen for that on a website and understand something's happened, or in your backend. You can find it, or other people can look at it, to verify you're not cheating with your contract, and then on top of that, we call this a init DiamondCut. Now this is where I was talking about that constructor, if you pass in a zero address for an init, then that's fine, it doesn't do anything, right.

It's like, okay, we're not going to do anything else, effectively, what's going to happen is. It's going to delegatecall to your init address, with the data you provide us, so that way you can call this constructor function at the end of a cut. I just wanted to make sure we walk through that a little bit, so you just kind of understand it, you don't have to understand it to its exact tee.

You just need enough to know, all right, I know the steps that are happening. Because when it comes to the deploy script, I know things can get a little bit hairy, you need to make sure you understand these things. So for us, we're really only doing a singular contract, so we don't need to do all this confusing stuff.

I'll keep cut, as an array just because that's what they already had, and FacetName, let's go in here and just go A, right? So there we go, we've just deployed A, pretty straightforward, in fact, I'm going to just take this out. And I'm going to this async function, deployA, deplayA, deploy A, return facet, there we go.

Just to let you know, make sure it happens and then we're going to move this thing in here, because, well it just feels better if we just do that. There we go, so now we will always know that A has been deployed to this new address. And so const, I think we called it facet = await deployA awesome, so this feels pretty good.

So now we've created this little deployment script, we've also created the cut that's required to add it to our diamond, we saw that right? So we have a action add, we have the address in which needs to be add, plus the functions that we want to be encoded onto our diamond.

All right, so now let's take apart this last little bit right here, so now notice what they do right here. This is that clever thing and I did it once too, where I'm going to grab the contractAt, in the shape of our IDiamondCut interface, at my diamond address.

Meaning that if we look up IDiamond interface, all it has on it is the function DiamondCut. So we're going to take our diamond and morph it into a single function item, because that's all we're saying is at this address. And we're now going to call it with our DiamondCut information, and address to call an init function on, and some extra calldata if needed.

So there we go we did that first part, hopefully this makes sense, we're just kind of casting an address into a shape. I'm going to erase these things we don't need this, we really don't need this, we also don't need this. Because this was just doing the actual functioncall, the init data, we don't want to have any of that.

So it really comes down to this last item right here, which is we're going to take our diamondCut remember. That's just the diamond address, casted into an interface. We're going to call diamondCut on it, remember, diamondCut is added because of the ether's object. It took in its ABI, which is this interface, was able to cast into the correct shape.

Then we're going to pass in the cut, which is just simply As, information right here, so I'm trying to intentionally overly explain this. Now we need to do one last thing, we don't want to execute any init function because our A, has no init function. So I'll just do the extremely convenient address zero, which of course an address is 20 bytes.

So I'm going to go, boom, 40 zeros, just a quick 40 zero right there, and then the data has to be an empty array. I wish it was a little bit nicer, I'm sure we can make it a little bit nicer. We could add our own function that kind of does that automatically for us if we don't provide any arguments, but there we go.

We're going to do a diamondCut and provide the zeroth address or the null address, and no information about it. So then it's going to wait for it to be finished, and once it's finished, we've done the diamond cut, we're good to go. I don't think I've added any mistakes, but I mean, you never know, so let's go like this, I'm going to simply go addA, there we go.

I'm going to remove that because I like to live dangerously, there we go, and let's see anything else in here, we don't need that. And I'm just going to get rid of this because honestly, we don't need all this protection stuff, so let's just make sure we return the diamond perfectly.

And we're going to log out all of our addresses as we go, so let's see if we can deploy diamond, and addA to it. I think we're going to be able to do this naturally first try, so let's make sure that we have our jpegdegens, still has our note up, there we go.

We still have our network running locally, so that means I can jump back here, npx hardhat run scripts, and let's just do deploy, network, local host. So if I've done everything correctly, you'll see that we've deployed our diamond at this address, we've deployed A. This is its address, we've even cut in here's the transaction that happened.

If we look at our network, you'll see all this information kind of coming in, fantastic. We've done a bunch of stuff, it kind of does a bunch of unrecognized contracts, it can't understand it. It's being called from the outside of this project a little bit confused, doesn't really matter because we know we've done it.

So, let's take our diamond address not our A address, remember A was deployed here, let's take our diamond address. Let's copy that thing out and go over here, and go const diamondAddr equals this, fantastic. So now instead of deploying the diamond or deploying A, we need to do something else, right?

Let's try calling A, so for me to call A I'm first going to go back here, open up A and let's go like this interface IA. Let's just do that, make it simple, there we go, we have the interface IA and of course interfaces always just assume external.

Even though technically it's not external public, is a superset of external so it works out, so there we go. We have our IA, everything looks fantastic, I'm going to go like this, a = await up we have to do this in a function, await, function callA. How does that sound, we'll call it callA, that makes most sense, await ethers.getContractAt IA, the name of our interface, plus our diamond address.

So notice that we are now taking our diamond address, and we are fully using a diamondAddr, there we go. We are fully using it as A, even though it's definitely not, it just contains references to A, so let's call it console.log "a" ,await a.getA. Is that what I called it, I always forget, this is the problem, you add too many of these, okay, I did use that, awesome.

All right, good, so we should see 42 correct. All right, awesome. So let's relaunch our script and see it do it, now notice I did something wrong. All right, there we go, await has no effect, that's because you're supposed to call it async, there we go, awesome. And then do this last one right here, there we go, we see our value 42, that's pretty cool, right?

Because we just have one contract that we're pointing to, but we're actually calling on a different contract, that's pretty amazing.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now