NEAR Live Contract Review | Episode 19: near.bet
Nothing is loaded from YouTube until you press play, and then without tracking cookies.
About this video
The application provides a marketplace for account names on NEAR. It has a unique price discovery mechanism that rewards all previous contributors whenever a new bet on the account is places.
We review and discuss the security of the contracts during this video.
Source code: https://github.com/Kouprin/accounts-m...
Follow the latest from NEAR Protocol on,
Website: near.org Discord: near.chat Blog: near.org/blog Twitter: near.ai/twitter GitHub: near.ai/github
#Blockchain #FutureIsNEAR #NEAR #NEARProtocol
Transcript
5,765 wordsCaptions as YouTube publishes them, usually machine-generated, so product and people names can be misheard.
- 0:01
hey everyone uh this is yuchin and today we have is chad william matt and mike and we're gonna record another episode of live contract reviews today we're gonna review um nier.bat it's a website that allows you to sell accounts on near mainnet so it's kind of like a domain marketplace and let me present so first my cpu is going crazy because i'm doing something in the background so hopefully it works and we can review the second contract
- 0:50
without an issue but um let's actually start with the first contract so there is a two parts and one contract is kind of a lock and lock contract that you have to deploy on the account that is for sale and this contract asks works like an escrow it it holds the account until the marketplace releases it and you need to remove all access keys first on your account that you want to do for sale so there is a special website
- 1:40
it would probably be better if i demo it but i'm not ready for this and once you deploy this contract and you remove all access key from this account it's ready to be listed then on the marketplace um you will be able to bet on it then claim it and also list them so let's first review the lock unlock account contract so it uses near sdk almost latest i switched to latest which is 301 and this is a contract
- 2:29
then we have an external callback to itself then we have a helper is promised success and it only has the owner account id so it has lock that only can be called by itself and it uses ignore state because you can do this multiple times so you can initialize this contract on top of that contract that was already there so let's say you're selling your account that has a multisig on it so it already has state so in order to
- 3:17
operate on the existing state it has to ignore state ignore this existing state um but this will overwrite the existing contract with this yeah it will override the existing contract as well as override the existing state key so this basically i'm done using it's like i'm done using multi-sig i want to sell it now yeah that's what i do it also has certain limitations such as um
- 3:52
limitation of the protocol such as you really cannot delete the state yet so in order to delete the state like entire contract storage state you have to um iterate on it and then delete each key in the individually or you can delete the account and recreate the account but assuming this is uh an account that you want to sell you don't want to delete it and try to recreate it because you don't control
- 4:34
near account so you would have to use something like another link drop call to to register this account again so someone can front run you or near can may not even allow you to recreate it because it's not aware that the account was deleted so it will be gone forever the the link drop so okay so it checks that it's called by itself uh which is kind of secure as long as this is not a callback that
- 5:15
can be triggered from this account now it initialize the owner and you have unlock which takes a new public key to add it verifies that the owner calls it i guess the owner in this case is the marketplace or the owner it cleans up the owner for some reason which is getting an empty string and then it makes a promise to itself to add a full access key and also adds a callback so what it
- 6:03
doesn't do is return this promise so you kind of will get a success uh but maybe this can fail internally so you'll get an early completion of the task so to address this you would use something like i want to promise and then return it okay toner returns the owner and the trick that we recently learned is you can do this so you can just deserialize yourself into consumable object and just return the string
- 6:52
instead of cloning it now this is a callback and you pass a predecessor question about the other yeah consuming self for a view call that's um it's an optimization like you're saving validators a little bit of computation but it wouldn't cost you anything anyway correct yeah okay looks nicer too so we can use the private macro on the callback right yep yeah that that's where the private micro comes into play
- 7:36
and this essentially elements eliminates the need of doing this manually so it's exactly the same thing so it checks if the access key was created and if it was not created we recover the owner id and return true or false well this boolean is actually never returned because this promise was not returned so the original transaction will not have a result of true or false but the receipt that was kind of kind of forked it
- 8:15
into this promise was doing it and that's it so this is very simple contract um it kind of has a access control by just doing this so like once you actually succeed in unlocking you are you're done and it attaches 20 tear gas on this coal which is probably not needed so five targets should be enough for this but it it does look secure so what it doesn't do is doesn't delete this account so potentially
- 9:08
so it doesn't it feels like there's still a full access key on this account that you're auctioning oh yeah so so this is handled by frontend because we don't really have a way to to go through access keys in the contract level so that's the limitation that we currently have and that's how the contract actually works so there is a front end that deletes all access keys including the full access key the final full access key once you've done this
- 9:39
lock it towards the marketplace and then at the end of this you the marketplace you'll be able to unlock it towards a new owner so then is the owner id that's always going to be like marketplace.near basically uh something like this yes well not necessary you can reuse this contract for other cases including testing but yeah it seems fairly simple and it looks like it's working and yeah and another like as we
- 10:28
mentioned a few cases it's like decrease the gas return this promise how do you know you can i can i know we've covered this on previous videos but how do you know that gas number off the top of your head we should actually add this to the best practices that you're doing like the gas management so the way you think about gas is are you doing any access or like any complicated work if not if you don't have any complicated state in your contract
- 11:01
you likely will fit fine into five tier i guess because it's enormous amount of compute for just a regular operation so this is probably around i know less than one giga gas so like around maybe maybe a little more like a few giga gas operations because it actually just in memory operations so like once you in in the method it doesn't cost you almost anything so there's cost on this uh on parsing it
- 11:35
car cost and decentralizing the contract and then cost and writing the contract back so usually it's under one tera gas for all of this operation so the internal compute even with state operations is usually doesn't really cost anything significant so that's why five targets is like upper limit that is usually safe on just basic compute storage operations and stuff like this now now when you do a function call so this piece does a function call this
- 12:11
cost around five tera guess so the function call itself costs around five tera guess so every time you do a function call and you promise towards the function call you need to think about i just added five tera gas um adding a full access key i don't remember how much it cost we probably can look into the runtime config to figure out how much it costs to to schedule a promise and this is a self promise which is supposed to be cheaper but
- 12:49
it's not and and adding a key is usually fixed size operation which is reasonably cheap now the only extra cost is dot then right now it's overpriced and it costs around temperature i guess so i add in a dependency on the promise costume targets and this is like completely overpriced so when we did the correct computation of how much it should cost it cost something like 50 giga gas so it's like two magnets cheaper than what it is right now
- 13:32
and the reason for this it's mostly covered by the extra promises that's already scheduled all the receipts for you so is that something that can just i have no idea this is probably a really dumb question but is that something that can just be changed in genesis being like oh actually change the number so yeah that's it's not in genesis you need to like actually do a protocol change that upgrades it live so you need to have a block before this to aware that nox
- 14:05
blocks is going to do a protocol change and the next block actually triggers the change in the config so all the execution goes with the new config and we i think we've done one config change so far but nothing major so we did something that we needed storage cost storage costs probably the one that was fairly simple change but same way you can modify any config the reason why we still haven't done
- 14:37
this is because we waiting on recomputation of function call so we want to bundle it with the rest of the changes which includes first of all default promise gets more expensive so like a basic promise call will get more expensive because we were underestimating the depths of the try so it will be like four times more expensive or something like this that makes certain actions cheaper
- 15:09
because now we shifted the cost into the base and like adding a new key is not as expensive then as i said it's super cheap but this one it depends on cost of deserializing the contract from the state so right now it is reasonably high and slow so we want to bring it down so there's some optimization work that this contract runtime is doing on bring it down to the reasonable cost i think sorry
- 15:45
right now it's something like 600 um giga gas for the like small contract but it may have to depend on the contract size or something like this or like have some like higher base cost and that will still drop it a lot so boss function call will be cheaper and then operation will be cheaper because in reality they don't really cost as much so major cost that we have right now is compiling the contract
- 16:23
and compiling the contract is currently assumed to be done at the runtime so when you call a contract that was never called before we assume you have to compile it and in the runtime right now we assume you have to compile every single contract every time you call it so we assume you have zero cash but what has what will be changed is the deploy action so the the action that deploys the contract
- 16:56
will compile the contract because you do this only once and deploying the contract can be a bit more expensive because the one-time operation that you do and you pay for deploying it into the cache and this operation will kind of compile the contract store it on a node in a compiled cache and then node will be able to deserialize it at the at the moment when you need to call it and that can bring down the cost of the function call
- 17:28
so overall the base will like the most common operations all of them will be extremely cheap compared to what it is now and it will not incur this like dependency extra cost that right now have so a simple method that does a call and the callback costs around like 25 characters right now because of two promises that it makes to like a remote call and a callback and a dependency that is temporary guess plus five targets to compute so
- 18:07
instead once we update it it might be like two tera gas for the promises plus dependency and five characters for call so you have only seven and that allows us to like rescale how much you can do the dependency like how fast they fly okay awesome let's go jump into the marketplace let's see if my problem disappeared build retry so we just published a simulator so that should work
- 19:08
okay so this is based on barry cards so some of the stuff may be familiar so you have profiles beats top bets top claims this to create a leaderboard number of offers so the statistics total commission acquisition time i don't know what is acquisition time owner id dao id contract extension oh interesting so this is probably for contract upgradability and so the way you would use it to upgrade your contract you can create
- 20:07
you can basically replace this with something else and because it's known right now you can create a second version of the contract so the bar serial serializer serializes this option into one byte that's kind of cool okay that's new so this is not needed in usdk it gets position time and dial id and set up a bunch of maps uabc defaults owner ids assigned already
- 21:09
what's up looks like we need a capital u 64. yeah it depends on like what what's the size of this if it's some nanoseconds then yes if it's a microsecond then probably not but yeah i wish you don't have to think about this okay so so the difference between two is the test new takes the acquisition time versus regular new text
- 22:11
owner id and it sets the acquisition time to some constant which is 70 hours 72 hours so this is the amount of time i guess needed to to get the account and it has that's the full auction time or that's just the like finalization time or something it's it's uh when you're trying to claim the account i assume well actually i don't know the mechanics we'll see i don't remember the exact mechanics um
- 22:54
so this is a bunch of statistics and and that's it so let's see something like beats actually let's go into maps so we have profile id which is account id and bid results bid id is also a candidate and then a profile is is a structure an account so yeah it has a bunch of stats per account so this is how like very cards was done so it had a lot of data on every
- 24:01
account level on the complexity it's kind of waste of gas but at the same time you don't need an indexer to to get this information if you want to okay so it has a participation an ordered set of bits and acquisitions it's uh what it did what this account did then there's a profile view um that's returns it so i don't think that's gonna work very well if you do too many actions unless you're removed from them as well
- 24:50
because this may run out of gas when you're iterating in the set not sure what bid id does because they both be the id so you don't really need this map and you can just convert it to to a vector so i think i'm not sure if an ordered set has to back but it looks like may have yeah in this case you can just call to back because they pause bid id
- 25:54
okay so i think again those those uh lowercase u64 is it for using json survey but anyway they cannot really exceed so they probably never exceed u32 and third is fine with u64 so it's a javascript that is not so like if we ever exceed this number that json on the javascript site then you'll have a problem but on rough side server is okay twist the long numbers so yeah these numbers are not balances
- 26:40
so that's why then they don't need to be converted all right so on rewards collected some callback okay so we get to the methods of the profile you can get a profile by profile id and it gives you a view you can oh i see no collect rewards so this is a method to to kind of claim rewards extract profile create for the predecessor then you get
- 27:43
available rewards it rewards more than minimum rewards then we return this message and okay so we set available rewards to zero we increase the profit by the number of rewards we say we save this profile of panic what does this even mean a third there was no no such profile so it basically verifies that there is no such profile i guess extract pulls it out right says fine so this is logic to to create two
- 28:37
extra and order it sets and prefixes based on the profile id but it does remove this profile every time so maybe it's like a safeguard or something like this okay so when you save this profile you want to do a transfer to the predecessor and you transfer rewards and then on rewards collected you have a callback that takes the predecessor in rewards and on rewards collected again it's a private method it checks if rewards were not
- 29:29
transferred then we return the rewards back we kind of roll back this operation and then we return whether it succeeded or not so it's kind of safety that you didn't delete your account and accidentally will lose your funds which is cool and here we actually return this promise this is good um so again this is two methods save update reward okay um cool anyway uh let's go to the next piece this is just action so what you can do and there's a ton of actions
- 30:22
so let's see first you can offer a profile id and no it's a offer so you need to attach more than offer deposits which is a lot of zeros so this is about 0.03 near at minimum we verify that you don't call it from your own account id then we do some beat so we actually haven't looked into what is a beat the beat is a bunch of bats which is a vector of a
- 31:28
can of profile ideas who batted participants and claim status number of claims okay so calculate the bet internally it iterates over all bets starting from one and multiplies it by the lengths so if one account gets too many bets that will be expensive now that will be expensive in terms of gas but that grows by basically 20 percent every time yeah but it doesn't read i read the bets
- 32:30
it's just um kind of like power formally yeah i don't want to throw too much revenge in here but last night i've been thinking a lot about just like this is a common problem we have to face is like i have a task that's going to probably take more than one transaction what do i do now and it seems like there can be like an approach that we can recommend almost like a standard um and this is a perfect use case of that where it's like you got to use pagination you got to
- 33:03
keep calling the transaction and maybe the transaction will say like there's still more to do or like we are done what we already done with this task or something like that yeah yeah maybe like save this computation into the state and then say you need to continue this computation right okay so force calculate claim price calculate bet price by two calculate claim price if it was not claimed then you can
- 33:37
calculate this otherwise what if none then this otherwise none yeah what's going on here i guess it but this one doesn't store it so so what's it magic here let's record comments are great uh calculate for free it's wait but it's calculating the price so it's saying if if there's no if it was not claimed then we're gonna do the compute if otherwise we don't do compute right so basically if not claimed then this otherwise none
- 34:45
otherwise it's not for sale so there's no claim price or it can't be claimed rather it was already like if it was already claimed then that's it but if it's not then here's the price um four feet if we get the claim status then we calculate bad price and then we say um time azer one what is this but this is okay multiplied by this number what okay let's
- 35:50
kinda to divide nanoseconds i guess okay uh commission for claimer for marketplace i don't know what it is but on acquisition if there was a claim then it returns whatever okay i i'm not sure what's going on here so there's a vehicle that goes through a map
- 36:54
what does the implement from forbid view give you where does that get used later it's probably when you do dot into and it returns you something like bit view in a here so like you do that into it doesn't it does acquisition time here just sums it up okay get bit gets you the current beat for the beat id get top beds it basically constructs it the key and iterates from a tree map to
- 37:47
to do this then top claims again just a vehicles this is helpers so again this is like more prefixes so you need to make sure you don't repeat them so that's where the new suggestion of using like a storage key will be helpful is it worth re-going over that in this video or will we just link to the previous one yeah we can in the show notes link to the best practices in the show notes
- 38:32
extract did or panic so again say peter panic clear beat so it clears this clears participants and sets claim status to none and now elections to zero okay so majority of logic is of this so this is just a bunch of helper and all of the actions apparently are in the action.s so get the bit uh we insert your profile into participants we get your profile then we say number of offers
- 39:31
save the profile so we insert the predecessor i guess you say who is the beneficiary but i'm not quite sure how that works okay um but an update i guess i had to look into the front end to better understand the mechanics what calls what because seems like this method might be called by the account that is actually listing itself so updating bad leaderboard uh but if it's called by that account why do you need to pass in a profile id
- 40:15
this is the beneficiary of the sale so who will get the the proceeds of the cell gotcha okay okay let's see what's going on here so we calculate a new price then if there's no bets on this then the current offer is initial one update commission what is up the commission we pay down the commission so initial offer goes to the dow and top bets
- 41:18
are removed so this is similar to um to bury cards if you remove first then we update the commission so interesting that this piece actually pays commission it's kind of like hey we're gonna update the leaderboard but also we're gonna pay some commission it should be called bet and update leaders and pay commission right so we go and update reward with this update rewards so we pay this profile some tokens
- 42:19
so for people who better early they get paid into some value from this so this one is 144 so there's economics actually that explains it but we can ignore it for now so amount of paid decreases by the amount we just paid so this can be actually extracted so you don't do this twice and then we pay the original better the first better the rest bad price minus the bad price divided by commission so we pay everything except for 20.
- 43:16
that's a lot okay got it uh and we increased the price so when you re-bet you kind of cover all the commissions of the previous batters then we insert this but on your profile then we update the top bets but we did not update the top claims so where is the top claims maybe here no so it's unclear like where's the top claims happening because we did remove it for some reason maybe it's not there at all
- 44:30
okay let's go back to actions i am in actions okay let's go back really far okay um so top claims are not really maybe you kind of cancel the top claim by doing it okay um let's continue there is more stuff to to do so when you bet we do we do compute um we get the bet we say on acquisition was it acquired you cannot bet if it was already
- 45:35
acquired so you need you have a time when the when you can claim it so if no one else bets that you will claim at a higher price then the bet goes away then the account you kind of claim it so you have 72 hours that no one will bet on your account that you're trying to claim if not then then then it fails so for fate commission do you know if the ui calls it bids or bets yeah it's kind of like a bit weird because like you have beats and bats
- 46:43
right it seems like bidding is the better mental model for this app yeah okay so we calculate how much perfect claim our profile and we give force calculate claim price very return and we also provide the forfeit so basically when you're trying to claim if someone else or outbid you they they'll pay you extra then you need to pay more for fit and commission then we say you didn't claim we say who participated extracting this profile returning
- 47:47
and we update the account cool finally claim so you we get a bit we calculate the the claim price we say that you need to attach more then we set the claim status save be the panic okay and we insert top claim which is double the price so i guess this 2x bad price was coming from this calculation but what if someone already like claim that where do we check
- 48:52
oh yeah here here we check that if someone already claiming it then if you want to claim it then you need to first beat and then claim it okay and finalize is the method that allows you to to finalize the acquisition so the acquisition should complete and you get the profile i guess you can use underwrap here like srf under app then you do get the profile you say you acquire this
- 49:40
this account then we update final result so this gonna redistribute the funds we remove top bets and we remove top claims so remove this from leaderboards and we clear the beat completely from storage so you now have this account that you can take so you acquire this so you pass the bid id we remove this acquisition it should return true um so this method probably should take one yacht and year
- 50:34
because you can kind of like it's kind of a method that doesn't require a touch deposit but it takes ownership of the account so ideally you you don't want to let it just do this with access key so we remove the acquisitions then we get the key why it's a string all right so we cast it back well you don't need to convert it then at all um so so yeah so this can be done using an external call
- 51:37
and what if the promise fails right so unlock essentially was not doing a promise in the past say now it does a promise and how much gas it attaches conductor i guess that should be enough right so this can be re-implemented a bit with the external method and finally this is what we reviewed update update final rewards we haven't looked so we go through all participants and we remove their participation we calculate the price
- 52:27
and then we go and pay them off similarly how we did before in the worst founder commissions one force anyway so it just pays off everyone who need to be paid and and that's it yeah overall kind of everything is secure except maybe in a few places you may run out of gas but that's it doesn't do any promises so you you're kind of safe there i had a quick question about that very first short contract we looked at yeah so there's like
- 53:24
owner id on there this is just like that really small one that you don't deploy to your own account yeah like does owner id get wiped out when you if you decide to buy a contract and then deploy something a new contract over it and it also has owner id on the contract structure yeah it does it does wipes it out during the unlock so like once you try to unlock once it basically removes the one already but if unlock fails it puts the owner id back okay i guess i mean i mean like if they
- 54:02
want to do a totally different contract on this they just purchased it and they also have like an owner id field is it going to freak out by like hey we're trying to add to the try yeah that's why like it uses ignore state so the better solution if we can actually clean up the state after ourselves so we can delete um deleted okay it seems like maybe ignore state is fine unlock and then on unlock you want to
- 54:33
clean up state interior so here after after this thing is successful like actually all right so i mean this cannot really fail um unless there is already an access key so what you want to do is you probably want to delete state here so this should be like some magic method that doesn't write stated and so which we don't really have um so it should be like like a view call but not a view call
- 55:24
and then it deletes the state by deleting the state key and here you may instead of calling yourself you just delete the contract by saying like it's too okay um so i'm a bit over time for my next meeting so overall it looks like it's working the the economics can be made a bit cleaner in a contract there's a lot of boilerplate around like how this is done where we grab the account then
- 56:13
then we update it and we have to set it back so it would be great to to maybe maybe one way of doing this you can use um some kind of azure lambda that you pass like one time method like update account or you grab the account wrapped into something so you extract the object that you know you're gonna put back at the end once it's dropped from from state but that's it for another time to to experiment with this type of patterns because this is very common like
- 56:57
how frequently you need to do this okay almost every single contract that operates on this type of stuff does this right now and this because we operate in like singleton for the contract and then all the methods are not all the objects like temporarily same for bits like here we got rid of bit so i would think the way you do this is maybe you have something like one option is you do this you you put it
- 57:28
into sub block at this moment bit is dropped from memory so maybe you have a version that is writes it into the state when the bit is dropped unless you call something like helper there so like no don't drop it like don't don't write it into the state or as i said another version is you you kind of call uh self beat this bit maybe and then you do pass the bid id and then you pass something like bit and you do a bunch of operations here
- 58:20
and like this so and here you take this mute bead essentially um could this be implemented with a helper macro near sdk rs yeah like part of a date app or something yeah part of the map it can be called update that does this thing i'm picturing almost something that's like update and then bid colon and like the thing to update bit but yeah then you need to have uh insert this so you need to insert this bit if it doesn't exist so this one
- 59:00
is like initializes with somewhat almost as default except you need this bit id maybe yeah i can see doing like just this default so it requires default and bit anyway um i'll stop sharing and thanks everyone for listening i hope it was entertaining and see you next time thanks