Building a Layer-2 NFT Bridge by Daniel Zachary Goldman | Devcon Bogotá
Devcon·Sat, Oct 7, 2023, 12:00 AM
Visit the https://archive.devcon.org/ to gain access to the entire library of Devcon talks with the ease of filtering, playlists, personalized suggestions, decentralized access on Swarm, IPFS and more. https://archive.devcon.org/archive/watch/6/building-a-layer-2-nft-bridge/ Cross-chain communication is one of the most complex and interesting parts of multi-chain development. In this workshop we’ll build out an NFT bridge from the ground up between Ethereum and Arbitrum covering all you need to know about bridging design and cross chain messaging. You’ll walk out with a set of smart contracts you can use to bridge your mainnet NFT collection along with way more than you ever wanted to know about bridging. Speaker(s): Daniel Zachary Goldman Track: Layer 2s Keywords: NFTS,Bridge,Arbitrum Follow us: https://twitter.com/efdevcon, https://twitter.com/ethereum Learn more about devcon: https://www.devcon.org/ Learn more about ethereum: https://ethereum.org/ Devcon is the Ethereum conference for developers, researchers, thinkers, and makers. Devcon 6 was held in Bogotá, Colombia on Oct 11 - 14, 2022. Devcon is organized and presented by the Ethereum Foundation, with the support of our sponsors. To find out more, please visit https://ethereum.foundation/
Transcript
foreign [Music] welcome to my workshop um today we will be building an nft bridge between ethereum and arbitrum if you would like to code along you can clone this repo here I don't know if the URL is visible enough but you can get it from the off-chain labs GitHub it's the most recently updated thing or you can grab it from my Twitter at these act 23. it's the last thing I tweeted um you're welcome to code along you're also welcome to to listen um uh both work most of the coding will be so we kind of have the skeleton of the smart contracts and we'll build out some of the solidity directly um and then at the end maybe we'll uh deploy it live on the go early testnet Wi-Fi is a bit suspicious it seems but uh we'll see um so um let me talk a bit about arbridge room first um so Arboretum is an ethereum layer too uh for our purposes we can think of arbitrum as its own blockchain because it is a blockchain um runs on top of ethereum and kind of inherits security from ethereum the uh the important thing for for building this bridge is arbitrum allowed a sort of arbitrary message passing between layers so we can send um I'm just going to leave the Twitter there for a bit so people can find the link um um we can send arbitrary messages sort of from L1 from an L1 contract directly to Layer Two uh I shouldn't say directly maybe sort of asynchronously we can send messages to layer two but arbitrarily um and likewise we can send arbitrary make arbitrary contract calls from layer 2 down to layer one and just sort of using those tools that Arboretum gives we can build kind of an nft Bridge around them and that's what uh that's what we'll be doing um so the um just to talk a bit about the architecture of the bridge itself there's obviously many ways to build uh an area c721 Bridge or something like it this will be very similar to the architecture we use currently on mainnet speaking of mainnet just to clarify this disclaimer here this is not tested code not audited don't to playlist on mainnet not liable whatever okay um code that actually is on man that um we use similar architecture for what we call our custom erc20 Bridge you can read more about that in our docs this will be a similar version of that um by custom what we mean is the token contracts themselves on both L1 and L2 are sort of deployed directly by you know a developer a user so um they'll be an ERC 171 contract on L1 another on L2 and as long as they have certain affordances they'll be able to sort of use this bridge uh kind of interact with this bridge um the basic functionality that we want from a bridge we can we should just say clearly in case it's not obvious um is basically we have we're sort of going from L1 so the L1 contract our our 721 will be the source of Truth we want to be able to um essentially use this uh the 721 on L1 and get a representation of a token on L2 that effectively gives us rights to the Token back down on L1 so you can deposit it and you're guaranteed rights to withdraw it um the the contracts involved will be what we call our Gateway contract so one contract on L1 one contract on L2 these are the sort of bridge contracts that are in charge of managing the mapping between the L1 token address and the L2 token address as well as handling the deposit and withdrawal functionality so I'll just sort of walk through the uh the flow real quick and then we'll get started filling out the code um but um so again the flow is we we have our we have our token contracts deployed on layer one and Layer Two um we'll see what affordances they need as we go um but there's kind of Three cross chain messages that are going to have to happen here so one is we the um the L1 token contract kind of has to register itself to the Gateway which basically just means it it kind of opts in to what its L2 address will be or what its corresponding L2 token address will be and we'll do that we'll just sort of store a mapping on the L1 side and the L2 aside that Maps L1 token to L to a token so that's this kind of registration so it needs to be able to make that call and that'll be a cross-chain messages so both both layers sort of know what token maps to wear uh we need to be able to deposit deposit in this case is going to mean sort of sending the token to L1 and escrow sending a cross-train message to L2 so our corresponding contract mints so escrow and mint is a deposit and a withdraw is kind of the opposite um we're going to burn the token on L2 send a cross chain message down to layer one which will eventually release it from SQL um that's the flow we need if you're coding along uh yeah you can get clone this and yeah let me get to deploying you can just set these environmental variables um you'll need an L1 gorilla RPC URL um and some uh private key funded with Gorillaz if you want to actually deploy it but um uh yeah for now let's let's let's dive in I'll get rid of my Twitter um okay so um to start we can sort of go so we're going to start with as far as the cross chain messages go we'll start with this registration step um so let me do this um so first of all just looking in our repo real quick um I'm just in the contracts directory here's our contracts that'll be deployed on ethereum here's the ones that will be deployed on arbitrum L2 and basically we have the L1 token itself dl2 token itself scale one Gateway the L2 to get where we'll talk about this Alias helper later just ignore that for now yes is that good mark a lot more yeah um so we'll start with so this is the token contract itself or an example of the token contract itself that we might want to use that we might want to bridge right we can see um this is already filled out so nothing to do here um we can see this is you know standard open Zep line Dr c721 um we're just giving ourselves the ability to Minge so we can mint stuff and play with it um the important thing here the important affordance that it needs is it has this registration method so this will actually make an external call to our Gateway and again this will update these mappings on L1 and L2 okay so that's already done the token has to be able to register itself um so let's start with that that actual registration um so if we go to L1 nft Gateway so again this is like the Gateway contract the bridge contract um so the key thing that we wanna that we're going to be sort of doing here is updating this mapping on the L1 side and then similarly there's a mapping on the L2 side that will catch with a cross chain message um while we're here so basically we just have a bunch of functions we need to fill out but we can just look at what we're initializing it with um so this is counterpart L2 Gateway the L2 side of the actual nft Bridge we're storing that and then inbox and Bridge these are both arbitrum core protocol things that we'll use for cross-chain messaging uh that we'll get to in a second um and then yeah this is this is the mapping we're actually updating let's just peek at the L2 side as well so the L2 Gateway um similarly we have an L1 to L2 token mapping that's what we're targeting as we update and uh yeah we'll talk about that other stuff um when it comes up okay so let's um let's start with this registration step so the token itself made this call right um to register itself and we want to update the mapping basically so let's um let's go for it do okay um so we are going to kind of assume that the caller is a token if it's not that's fine it won't affect anything else um so we'll call this L1 token then just grab the address because we're going to use this in a few places hopefully my hands get slightly less shaky in a second um so there's the L1 token address that made this call and now we just update our mapping um there'll be a check we want to do but let's just let's just get this code in first L1 to LG token okay the L1 token mapped to the L2 address provided cool that was easy um now um one question we should ask here is should you be able should the token be able to sort of re-register to a new address and we can decide you know for the sake of Simplicity let's say no this is kind of by the way this this whole architecture is meant to be as simple as possible kind of minimum viable token Bridge so we can kind of focus on the cross chain messaging stuff and understand what's going on so if the Gateway you know if the token's allowed to sort of change itself to address that could make our assumptions kind of weird um but another thing we might want to ask is can it re-register itself to the Sam address and we actually do want that and I'll explain why but first let me let me write this check so um if the um so we're going to check um um actually let's do this yeah so before we register it let's grab we'll call it like current L2 address and we'll grab it from the mapping so before you registered it here's what the address was yes and we're going to say if it's all we're going to check to see if it's already registered um something I'll figure that out um check and see if it's already registered Excuse me yes I have a more high level abstract question here but specifically here are you require for nft smart contract to um be compatible with your Bridge fundamentally because they need to self-register right yes and if I'm an nft publisher why do I need a bridge I can just click create a button on my website like burn photo can and then give a tokens when you buy no Bridge needed uh you could in that case it'll be trusted right so this we want it yeah but nft fundamentally our tokenization of Rights so fundamentally nft are trusted well if we want the bridging process itself to be trustless let's say right so correct let's put it that way right so the bridging so yes whatever trust assumptions are within well if that's as last as for cross-pressession yes exactly so again we're treating the L1 contract as the source assuming yeah it's a good question so L1 contract is source of truth whatever trust assumptions it has we're sticking with but the bridge itself the bridging processes compatible with a legacy contract it's only for upgradable contracts or new contracts specifically built for interact with this bridge that's yeah that's right if you have an old NFTA for example what you could do is deploy a new contract that's that's kind of a wrapper and use that one yes exactly okay so in in terms of in terms of re-registering so if it's already registered we're going to actually allow it to register again as long as it's registering to the same address um as strange as that might sound um so we're going to acquire that the current address is equal to the new one and the reason we want to do this as we'll see is we're going to be sending a cross chain message um I'll just keep shouting okay um let me know um the okay so we need to know the address that um at which we want to trigger a message on the L2 side which is going to be our L2 nft Gateway which we have stored as counterpart Gateway and then we also need to encode the actual message we're calling right so on the L2 side just peeking ahead the message that we're going to be calling just because we have the name here finalize registration from L2 we need to sort of encode that and specify that that's the that's the method we want to call so let's uh let's do that and encode that data and we're going to actually just do that in a separate method here get register L2 message call data so here we're going to encoding the method of the call that's going to be made on L2 um so we'll use [Music] um encode with selector right so this is just this is you know um um this is what the origin cross chain protocol expects we encode the actual uh the function name itself we've already imported this thing so it should be yeah L2 then if T get away um um what was it called finalized registration from the one that's the method we want to call so there's our function um and we pass in its parameters which are just going to be these so basically these are the things that we need at L2 to do the second half of the operation which is updating the mapping at L2 um cool so that gets us the call data there's a reason I'm putting this in its own function this is going to help us estimate gas later basically but for now if that's not satisfying we can just say it looks nice in its own function easier to read okay um so that'll be the call data for our cross chain message call let's grab that in our registration method um yeah and uh we pass in what did we call it L1 token L2 address uh let's call this something L2 message call data cool um okay let's just like see if this compiles um just to make sure we don't go off the rails cool okay um okay so now we have our call data now with that we can actually send a crosstain message so I'll just briefly show you um the interface for sending L1 to L2 messages so this isn't in the in the repo this is um in the arbitrim uh uh the Arboretum core protocol Nitro Reaper but just to show you the interface here in fact let's do this that's like reasonably visible kind of um um here's all the things we need um create retryable ticket is how we send an l12 message um let's just let's start filling out the fields and then we'll talk about um all these new ones but for now okay so um we have our inbox address saved again this is just core protocol um create ah it's Auto filling everything um I don't know why it's doing that okay um okay so two is uh the address at L2 at which we want to execute the L2 call so this is our other Gateway counter Portal 2 Gateway maybe I can do something as there's more it should um become horizontal um or vertical rather the okay so we have that um again this key thing down here this is the actual message that we encoded so we have that now everything else has to do with kind of paying for gas at all too so the way these retrieable tickets work is we not only specify a contract call we also fund it so we literally you know we're effectively depositing ether um along with this message we're going to use that ether at L2 to do the execution so a lot of these other basically all these other fields have to do with what's going on in that L2 execution um so um L2 call Value that's easier because that's just zero in this case we're just updating a mapping we don't need call Value on the L2 side um and now we have all this stuff that has to do with gas um so I've just included those in this L2 gas param struct let's just look up at that but basically L2 execution so max gas and gas price bid this is very similar basically the same idea as gas price and gas limit on L1 right works the same way on arbitrum the one additional thing because this is a cross chain messages there's this special Max submission cost thing an additional fee for processing cross chain messages because there's just a bit it's a bit more resource intensive so that's like proportional to the amount of call data that you use but for intents and purposes here we're just we're asking the client to provide all these values so we don't have to worry about where they came from um but those are the those are the fields we'll need to to create a cross chain message um and I should just do this now as well um like I said we're sort of depositing The Ether along with it um so we're going to send that along um is that right um that looked weird to me but I think that's right so we send this along this will pay for the L2 execution the L2 will sort of automatically try to execute it if it fails the execute on the L2 side it can be retried for a week that's why it's called a retrievable ticket and this gives us kind of an atomicity guarantee um anyway let's keep filling these out so we have the O2 counter bracket with no call Value and now we get to these L2 gas params um so that was just passed in can I make this uh I feel it's getting weird I'll just type it up um so max submission cost is next um okay now we see over here we have these refund addresses and this is basically on the L2 side if you provide excess gas in your deposit you specify which address you want that guys to end up on and this is necessary basically because it's a cross chain message we need to sort of do something with this excess gas uh yeah question they also call Value yeah yeah so I think of L2 call Value when this cross chain message we're creating this effect something that's going to look a lot like an L2 transaction it's just going to appear at L2 unsigned basically okay so for that L2 transaction we need to specify kind of all the usual fields okay so but the we'll we'll sort of build out the contract call in a second and we'll see what the L2 transaction is doing but here we're specifying all the necessary things so we have like the contract call the address and then yeah the actual call Value just on the L2 side um yeah hopefully that makes sense if not hopefully it'll become clear in a second when we see the full the full flow um yeah um okay so the refund address um we can just we're asking the client to provide so in this cross chain message sort of where your excess ether gets refunded at L2 um and then there's a second refund address which is if there is call Value you specify if the L2 message never succeeds you specify where that call Value gets refunded at all too in this case doesn't really matter because there's no call values we'll just use the same the same refund address and now we have L2 gas params uh hard to see what's it called gas Max gas so that's kind of the gas limit and then L2 gas params um it should be something like what do we call it Max fee for gas I think for gas price there we go gas price bid okay um let's get rid of this hoping this will just horizontally um okay and then finally the last parameter was the actual encoded uh the encoded call data of the L2 message there we go okay um we are complaining yes I did mean that okay looks good um so there's a question message um this gets published and this will sort of automatically execute this method that we encoded at L2 so let's go to that now um which if we remember was this thing finalized registration from L1 so this gets automatically executed now we want to finalize the registration which means basically the same thing it did before we're just updating this other mapping okay so that both sides of our Gateway know uh what token goes to what um so we'll update the mapping l12 token the L1 address that I got from the cross chain message uh gets updated with this L2 address um and that's basically all we need to do there um now uh other than one other important thing which is we want to make sure that this method at L2 can only be called as a cross chain message otherwise this whole thing breaks um so we'll add this only counterpart Gateway modifier and now we have to fill that in I think I still have to do here no more to do um so only count up our Gateway this is um gonna be a simple required check just with a slight gotcha so here we're going to ensure that the message.cender equals this thing that we're calling counterpart Gateway L1 or counterpart L1 Gateway aliased so notice that we initialize the contract with the L1 Gateway and then we do this thing where we Alias it um I'll just just to peek at this real quick the actual aliasing step you'll see is just like adding to the address itself some constant offset so what's going on here is um when it comes to cross chain messages the way that the sender address appears is not the sender of the L1 contract and this is basically so that on the contract level we can easily differentiate between a con messages coming from L1 versus messages coming directly from L2 we need to do this because arbitrim contract addresses at Arboretum are computed the same way they are um the same way they are on ethereum so we can and often do have two different contracts you know one at L1 one at L2 that share the same address so if you can't differentiate between them you could get all sorts of weird spoofing attacks in this case every cross chain message kind of lives in its own address space this Alias space so we know this is definitely coming from the L1 Gateway um cool there's our registration step um um yeah any questions there because that's that's kind of yeah if you didn't yeah so if we didn't do the like if aliasing wasn't part of the arbitrine protocol for example if the message.sender was just the address from L1 then the uh the deployers of this contract let's say if they were malicious they could deploy something else at L2 at the same address that and then you know it becomes very hard to reason about the risks there yeah um so this way it's protecting you from malicious developers basically which is a good thing to protect you from anything else cool okay so there's our first there's our first cross chain message just simply doing the registration um let's do the deposit so this will also be an L1 tile two message um and uh it'll look fairly similar actually let me just make sure this compiles again because I'm not using remix and I'm paranoid okay um I'm just running just compiling with hard hat um cool okay so our L1 token registered so now we're going to assume or we're not going to assume anything but if this token is indeed registered it can do a deposit um let me just make sure I didn't forget anything cool okay so um now we're gonna deposit so you can see it as once again the L1 contract is the source of truth that's what we use so that's what's passed in as a parameter um so we'll use that to look up the L2 address uh first so let's say L2 address equals uh we'll look it up in the mapping but we just saw the registration step go down and yes um and now we're just going to make sure it's actually registered um so just require that this thing is really an address L2 address um register okay um so cool if our token is registered our L1 token opt-in we inherit whatever assumptions Helen token is making again um now we can proceed so we can actually do our our um our deposit so deposit again and tells these two things we're going to Escrow the token here send the token to this contract um yeah we have our 721 721 interface imported um so we'll uh connect to it at the L1 token address and do save transfer from uh from two token ID yeah so from the uh from the sender the caller will deposit to this contract this is the escrow so here here um and the token ID yes cool so there's the first half of our deposit and now very similarly we're going to send a cross train message to L2 um again we're going to need so we have similar parameters here the gas Brands the refund address all the sort of gas specific stuff once again we need to encode the call that we're going to make on L2 which we will do in a function here um so this is basically again just think of this as like the information that we need to send to L2 and the actual function we're going to be triggering on L2 is called finalized deposit and there it is okay so let's encode the call to that now again as a separate function um code selector awkward autofill um so just like before awkward autofill um and it's the same parameters in the same order uh so yeah function selector here and it's just yeah all these so this gets encoded um yes now let's grab that um so we have our uh what I call it before a little two message call data or something in fact I want to call it the same thing so I can be lazy later I'll do a message called Data yes um and we pass in the parameters so this was L1 token L2 address token ID and two two is the sort of um the address at which we'll be depositing the token the sort of receiver of the token at L2 um okay cool so we have our call data and now um once again we're going to send a sender a tribal ticket cross-chain message in fact in the interest of time and being lazy I'm just going to copy and paste this exact thing because it should be all the same parameters I believe I don't see why not um so we created a travel ticket yeah Sam browners always love the same L2 gas business once again the L2 call Value we're not sending any ether uh or rather we're not you know using any ether in the um in the L2 side of the message we could if we wanted to have the option of like deposit your token and some ether we could add a parameter there but we're not um and then yeah the only difference from before is we just calling a different method um which we did here um cool so now we will finalize our deposit and finalizing deposit means minting at L2 so let's do that um so now we're back on the L2 side um and cool we want to check here that it's properly registered um there is something that looks like an edge case which I can talk about in a second why it's not really a scary Edge case in terms of the timing of registration and things like that um what am I doing um yeah so we're going to look up in our mapping uh yes and see that that matches with the token provided so this ensures that it's registered on the L2 side yep um and cool that should be it um once again we'll add our only counterpart L1 Gateway that's kind of the important thing so it's gonna only be called via that cross chain message um and okay now we can do the L2 part um the L2 part of a deposit is minting so now we now we want to actually look at our L2 token which we haven't even looked at yet um what is going to be deployed on the L2 side so here I'm this was you know not this was deployed directly and basically this is the Assumption this is the assumed interface it has to have these methods here yes I have a method that allows the bridge to Mint and allows the bridge to burn uh and we can just fill that out now um fairly simple mint okay so we expose the ability for only the uh only the Gateway or the L2 the L2 bridge to Mint um I think it's in that same order I'm not mistaken I think that's how that works um and let's add this only Gateway that should be probably be clear if it was called like only L2 Gateway or something but when we initialize our L2 token we give it the address of the LG Gateway cool so now that's exposed dl2 get the L2 Gateway can uh convince so let's uh let's mention um where are we finalize deposit yes so let's uh connect to our L2 token um or rather [Music] um yes let's connect to the interface but we'll use our assumed interface um L2 our vrc20 which has the bridge mint and Bridge burn functionality uh yes I'm sorry the L1 and L2 Gateway are they depleted it doesn't matter yeah it doesn't matter they don't need to be yeah so just as long as we as basically as long as when we like when we initialize these contracts we know they know each other's addresses we're fine so there's no need for them it's probably better than not at the same address because that tends to confuse people we've had issues actually um um yeah but um so they can be if you're if you if you want to be fancy with how you'd apply them but they don't need to be um okay um what are we doing we're minting yes and my editor is smart I think it's not the right order yes cool um so there's our actual mint um and that's it right because again this this can only be called as a cross chain message so there's uh there's our deposit yeah any other questions before we move on to the last um time is fine so let me let me slightly speed around this but um um let's um let's do a withdrawal so here we're going to [Music] um need a cross chain message this time from L2 to L1 I can just show you the arbitrine pre-compile we're going to use which is called our obsessed so every Every arbitrum Chain gets this pre-compile it lives at um address 100 or that hex um and this just exposes all sorts of system level functionality for our purposes we have this send transaction to L1 very similar idea right we specify the L1 address that we want to make some contract call and we give this this data this call data which is the encoded message that we're actually going to call notice in this case there's none of that gas stuff um and this is because essentially there's no such thing as automatic execution on L1 so this has to just happen in two steps right we initiated on L2 and then sometime later the client will sort of finalize it on L1 via its own transaction so perhaps slightly more inconvenient for the user but convenient for us now we don't have to have those parameters so it's a two-step process um and this is where you have to wait the optimistic roll-up delay period if you're familiar with that stuff um okay cool um so let's do our let's do a withdrawal here um yeah let's go for it so let's see we have like 15 minutes that should be fine um let's check to see so we're initiating this on the L2 side once again we we have our mapping that is the source of truth of registration so let's uh check to see that the thing is registered and also get the L2 address so again we're specifying our token by the L1 address that's always the source of Truth um and here I think we just need to see that it is registered yes um just like before looking for how to address require that it exists plus autofill um okay um so now okay for a withdrawal we want to burn first um so let's fill that in back in the token we want to expose the affordance for the gateway to burn um so let's do that in this case we also need to actually verify that you're uh that you know this owner has the right to burn it which we didn't have to do for mint um basically in order to get to the mint code path you had to transfer a token to begin with so there we were safe that you were the owner in this case you just gave me a token ID who the hell are you um you better be the owner so let's make sure you're the owner um so the Gateway will be responsible for passing in the owner address and now we want to I think it's owner of I'm blanking out that's how I understand um I think that's right um what's that is that right yeah thank you um so we want to acquire require that it's the owner passed in let's call this like whatever it's like potential owner should be owner whatever um yes cool so we make sure that this so yeah again the Gateway is responsible for making sure that that owner address is passed in we make sure you're the owner um and then okay once we are we can we can go ahead and burn yes maybe that's a nicer way to do that but that works um I don't think there is um okay so now we've exposed Bridge burnt oh this also has to be on the Gateway of course would have been critical vulnerability um okay so now we can burn um yeah so we connect to the Token on the L2 side of the address we just got from the registry we call Bridge burn we pass in the owner or claimed owner which will just be the caller of this method um and the token ID does that work yeah yeah okay um cool so we burn on the L2 side now we want to send a cross chain message down to layer one um which once it reaches layer one we'll release the same token from escrow same token we're just notice I didn't say this explicitly but we're just sort of tracking tokens by ideas we're just keeping the same idea two as it has on L1 because why not um okay so we need the destination address which we have the L1 counterpart Gateway um let's do what we did before and encode um a function call here because we'll just do it in line not as a new method okay so what's our function and what do we need to send is the question in order to sort of release it from escrow on the L1 side um we can speak so our method here is called finalize withdrawal we specify these things um so let's encode that on the L2 side um I forgot them already we specify oh and token token ID in the two address okay and it's called finalize withdrawal um um yeah uh Owen token e oh and let's call it something so this would be the L1 message call data in this case um that doesn't oh selector yes this will be encode it with the function selector on my linter just be nice it will not oh well um okay so this is the message we're encoding to send to L1 and now um there we go is that compiling yes um now we will uh yeah we'll send a message to L1 with the arbsys pre-compile yes yes Yeah so basically that's a good question so the question between here was like how do we ensure that this mapping is legit essentially right like how do we know this isn't the actual mapping and basically that's the registration step ensures that so we're we're inheriting whatever trust we give to the L1 contract only the L1 contract itself can register so the L1 contract the token contract itself is the only thing that can self-register and it specifies the L2 contract so if it lies it lies but that's right then that means that the elwind contract was malicious or if it gives the wrong address that's its fault okay so we're assuming that this L1 contract over here when it makes this registration call picks the right address on the L2 side right I guess yeah I don't know if that really answers your question but I think maybe the key thing is only this register call it can update the mapping and only the token itself can affect it um um I hope that's helped um okay where are we here oh yeah now we have to send our send our actual message so let's do that here's our message um we have this Arab system by the way I'm just you know in the interest of time not showing but all this stuff is imported these are some uh this is like our library um where we publish these interfaces but anyway um so again it's just a pre-compile at address 100. and our method that we're using is ntx to layer one um the address is the Gateway contract counterpart Gateway or whatever it's called counterpart L1 Gateway not the earliest version this time we want the actual address um and we'll send the call data um what am I doing oh address sorry there we go address 100.
and that looks right to me yes um I think that's all we need to do here I think I missed a return statement or two earlier um cool um okay so let's uh finalize that withdrawal um this part should be fairly simple um so what does it mean to finalize the withdrawal this means just release it from escrow um well okay so the first part will be simple um let's release it from escrow so transfer it out to the uh the destination address or the uh the two address I should say so we connect um and we transfer um from two token ID so from here right it should already be escrowed sorry um too much coffee very jittery to um and token ID so there's the actual um there's the actual method that gets called now uh the last thing to do um is uh uh fix this error oh I did something stupid um there okay um the last thing to do is to make sure that this actually only gets executed as a cross chain message um so this is on the counterpart L2 Gateway this is a bit more involved at least to understand the pieces of it and I see there's eight minutes left so maybe I'd rather if there's any other questions feel feel free I feel like maybe that's but if I don't see it just shout yeah yeah the doodle contract itself will do the registration and it only has to kind of register once is the way to think about it right so that so once the contract itself and it's not it's not necessarily the owner of the contract I don't think of it that way it's just however the contract is set up the contract needs to be able to register so in this case it's not the owner um in fact in this case anyone can call register token Dell to which means in this case actually it's probably not the best idea you have you should like deploy this contract and register right away to the right address but as long as some as long as the function can make the external call to register they can register and whether that's you know whether that's proper whether that's malicious you should look at the L1 token contract so that's yeah um probably the doodle team right so yeah so who like whoever so somebody basically like whoever sets this up right like we're probably the same party that deployed the L1 side um we'll sort of plan this um so they had to play both and you know the reason you want you wanted to play both is so you just know which address to set okay but the other one but the other one contract ultimately has has control we said yeah no so good question in the happy case in the common case no the question was when we when we deposit yeah um does anyone need to call a function on L2 so the way these retryable tickets work is already Richmond cross chain messages is it should it'll execute automatically it'll try to execute automatically so we Supply the gas for the L2 side to execute um and assuming that doesn't revert the user doesn't have to do anything else so from a user perspective they publish an L1 transaction they wait a few minutes and then it just it'll get minted on L2 now we can't guarantee the execution will succeed at L2 like maybe there's a gas spike in the time between when you deposited and when there were tribal tickets created at L2 if it fails then someone's gonna have to kind of poke it again to re to re to retry it to re-execute it be able to wake to do that but normally yeah in the happy case that shouldn't happen correct correct we Supply it Supply it here so actually so when we do this create retrievable what this is doing create retrievable ticket behind the scenes is this actually depositing this message.value ether okay so this is so that's that's that's happening behind the scenes so this so this this value that we send is actually getting deposited to L2 and that has to cover all of this gas cost that we're supplying so that has to cover you know uh uh you know gas price times gas limit plus Max emission Cost Plus call Value yes correct the L1 yes the L1 call supplies the gas for L2 now by the way as like a side note if you already do have gas at your L2 address there's ways you can you can use that but this is this is the path that we encourage for doing cross-chain messages is just Supply the user directly for various reasons yeah um um yeah cool then you can if you look at our docs you know I I brushed over some of the details of retrieable tickets because um yeah there's a lot going on there but that's that hopefully gives you the gist um any other questions I'll just start writing out how we do this in the meantime okay might as well just finish this because it's um so here's how we're actually guaranteeing that the finalization of the withdrawal is really a cross-chain message and here this gets a bit into the weeds of how the sort of cross chain messaging contracts work in arbitrum but basically cross chain messages all come from this bridge contract which is a protocol contract um I'd like to show it to you if there's time um maybe I'll just show you the first part um that's that's the ultimate caller let me show you the flow why not so crosstain messages just real quick um if we look at how they're executed so they're initiated on L2 as we saw in the case of you know you're gonna have to wait for the dispute period because it's an optimistic grow up um and then anyone can call this execute transaction thing so in this case if that's not automatic the way where tribal tickets are there actually is the second L1 step um the thing I want to show you in this execution here here what we do is we um as we're executing this cross chain message we save this context thing so we can expose all sorts of data about this what's happening you know what happened from L2 notably this sender data is what we're going to need so we expose that in something like element L2 sender but during execution it's a little hard to show I guess but during execution the point is as we're executing a cross-chain message we can access this context so we can do the checks that we need um so that's going to be important that's how we'll ensure that it's really coming from the L2 Gateway and then finally um the thing that actually makes the call is going to be this other contract that's the bridge so that's always how L2 to L1 messages work so we ensure that it's really coming from the bridge and then we would also ensure I'm sure it's really coming from the bridge that's half the step half of the work the other half is making sure that it's uh we grab that context and make sure it's really coming from um the address that we expect um let me just throw that in here I think there's time um so that this is at least complete um so we'll grab that out box contract and this is uh we can just do this in one line I guess slightly ugly but we can instantiate the bridge and then the bridge gives us access to the active outbox this is just so we don't have to store another uh another address um yes um that looks like something might be off I don't answer uh attention this is hard to do live um I didn't call it anything yes thank you and then I think that's also messed it up um yeah I'm gonna sing a oh at the end yeah that should be yeah we're instantiating after we call there we go that's the address okay so we're just grabbing the outbox address which is stored in our protocol contracts um and now uh we can get the L2 sender via this neat little context trick um what is uh L2 element Center how to to L1 sender so that updates during the context of execution and now we want to make sure that that thing um is the counterpart gateway address basically uh from Gateway okay um so because there's no automatic execution because this doesn't work the way you know tribal tickets work checking out the sender is where we expect it to be is slightly different but this tells us yeah it's from the address we expect and it really is a cross-trained message um cool since we're almost out of time you know so there's if you're coding along or if you want to grab the repo there's a branch that has like a completed version I can also push this one but there's um scripts to deploy the contracts I can even just really quickly run then um I don't think there's anything much to show there but then there's another script that'll actually do a registration and deposit so you can mess with it I'm gonna wrap it up okay um but uh yeah cool if we have to stop there I guess we'll stop there yeah cool uh thank you all for listening
Automatic transcript — names and jargon may be misspelled.