Skip to content
by Unity Nodes
NEAR Protocol

NEAR Live Contract Review | Episode 13: wNEAR - fungible token

about 53:06 long

Nothing is loaded from YouTube until you press play, and then without tracking cookies.

About this video

wNEAR is a fungible token that wraps native NEAR token in order to be compatible with any contract/factory that accepts fungible tokens.

The wNEAR contract implements the new fungible token standard defined in 3 individual standards: NEP-146 - for core fungible token API https://github.com/near/NEPs/discussi... NEP-145 - for storage manager per account https://github.com/near/NEPs/discussi... NEP-148 - for fungible token specific metadata https://github.com/near/NEPs/discussi...

Source code is: https://github.com/near/core-contract...

// Also this video is recorded in Zoom instead of hangouts, so it has slightly better video quality that should help with code readability. Eugene promises to replace his mic in the future with something legit.

Follow the latest from NEAR Protocol on,

Website: nearprotocol.com Discord: near.ai/discord Medium: near.ai/medium Twitter: near.ai/twitter GitHub: near.ai/github

#WhiteboardSeries #Blockchain #FutureIsNEAR

Transcript

6,139 words

Captions as YouTube publishes them, usually machine-generated, so product and people names can be misheard.

  1. 0:00

    out hi everyone this is another episode of live contract reviews and today we're recording in zoom so you might see a higher quality and at least this is what i hope for today we're going to review a wrapped near contract that i updated from map 21 towards a new fungible token standard that we're currently still developing it mostly finalized and the only piece that is not finalized is metadata and this is fungible token specific metadata

  2. 0:42

    so um let's go into this and i think this video will be great to actually understand the new standard and how it works first of all what is wrapped near or up near is similar to ethereum's wrapped east and that allows you to represent near token as a fungible token so when you're writing a contract that interacts only with punchable tokens you can use for appnear and it will work the same way as any other fungible token

  3. 1:22

    but under the hood it's actual near and represented one to one so the way it usually works is you have two methods one to deposit near as a touch deposit and that will rub the sneer and mint new tokens essentially and the contract will keep this near on its own balance but your account will get tokens and the second is withdraw so that essentially burns the fungible token near

  4. 2:00

    the fungible token amount and returns you near to your account okay so there is the structure of this contract is the following we essentially have three standards that are represented representing a fungible token standard so it's composed of current law of three standards we have a core api uh originally was in 141 i think latest in 146 actually um a storage manager the generalized account registration

  5. 2:46

    to um to provide storage allocation uh variable storage allocation so the way it works is um because nir requires you to keep some amount of mirror for the amount of bytes that your account keeps so for example you deploy a contract you need to lock some amount of near given the price per byte multiplied by the amount of bytes you occupied on this account so when you deal with a contract that is have multiple users and multiple accounts which is majority of contracts

  6. 3:37

    are going to be like this then you need to think about civil attack by the users that will try to drain your available storage so let's say you have a 15-year on your account and you use certain year for storage so an attacker can try to block your contract from taking more transactions by pre-allocating storage in your contract state by issuing a ton of transaction that increases storage

  7. 4:11

    in fungible token example you can basically if there was no account registration or storage managing then you can send one your year to a million of implicit accounts and that will increase the storage more than amount of gas you would have to burn for this so you would pay less in terms of near than what you will use on this given account even with 30 gas refunds uh you will be able to join the account that's why we designed the standard that allows you

  8. 4:51

    to pre-allocate near uh for your balance uh that would be used only for storage so essentially you can somehow like say hey here's one year that you want you can use for my storage on this account i will get to this finally their third one is a metadata specific to the token we didn't want to put it into the core because this metadata may evolve so for example we may introduce an icon there uh that's not present yet and the new fungible tokens

  9. 5:31

    metadata will include it while the old one does not we cannot really change the previous map once it's finalized so it has to be a new standard and another method another uh file is brought near so it just has a logic to wrap and near and unwrap near and finally some internal methods that we use for fungible tokens plus some helper methods okay so that's a high level overview the way we do imports are like this

  10. 6:12

    uh we use some crates that has to be exposed as pop use crate that export all methods to the module and some crates that don't need to be exposed outside of this module uh don't have fun then modules and i like now to rename my all of my contracts to just contract instead of like giving it specific name so because it's a singleton it makes sense to to call it a contract so like every

  11. 6:44

    contract i use i will call it contract now so it uses the current life near sdk not the one that will publish so it doesn't have the helper for example for panic on default and it has three methods or three arguments so first the lookup map is a map from account id to balance if you remember our previous fungible tokens i used hash in the past because it was fixed size and it saves amount of bytes if your account is longer in this case i decided to not do this

  12. 7:31

    because it will help you to if you want to get all balances of the rap near you can still do this by clearing a state of um of uh of the contract so you cannot iterate on the contracts or the vehicles what's up so you would look it up externally by you know using the state api with near yeah ui yeah it's similar how we use your methods right you usually don't need to iterate on it

  13. 8:11

    like view methods are designed in theory they should be designed to be called by other contracts but in this case we use view methods mostly externally anyway by by calling some public rpc endpoint so when you query in a state you probably want to also be able to like query this and parse it later if you hash the account id then you cannot do a reverse lookup unless you use indexer to figure out who created this hash

  14. 8:51

    plus 32 bytes um well yeah so the difference here is account id is a string so it's not fixed size so it's uh 64 bytes maximum for the size of the account plus 4 bytes for the size of the string so it's 68 bytes instead of 32 bytes excellent but we don't need like maximum yes account like that's probably going to be less than 32.

  15. 9:26

    yeah but i will still charge your account for full size to avoid small complications but it's up to sure yeah so yeah then total supply is the amount of near that is minted so hold it by this account wrapped into the tokens and it's variable for this country for this functional token finally account storage usage is a constant that i compute at the beginning of the of um at the utilization so i

  16. 10:05

    basically create one fake account and then i store it i calculate the storage and then i remove it from the storage and here's how i do this so i get the storage i create this a new account which is i'm using i'm safe i don't know why um then i inserted it into a map then i calculate the storage that it took and then i remove it from the map and and i returned so i initialized my contract so what happens here is it basically just

  17. 10:53

    puts a string straight without having to do from utf-8 so it doesn't need from utf-8 because it's already utf-8 because i know about this just a way to initialize string i probably can do string from and like put all this 64 ace there and that will let me simplify this a little bit but i i afraid that i might make a mistake so our total supply originally zero so when you initialize this we don't really need any title supply because no one owns any brought

  18. 11:41

    brackneer okay let's first go to rapneer so this is a two methods unique to this contract they allow to deposit near to this draw near so first both of them are payable even withdrawal and this is the security feature that we currently use until wallet and our wallet sdk supports this security feature on an access scale level so restricted access keys at near cannot call a method that requires a payment

  19. 12:30

    that prevents someone to create an access key to this particular contract and issue a transfer for example from um from their gap in order to do this transfer they have to call the wallet and the user has to confirm the transfer now when we design a wallet we can uh embed certain apis there like we can embed ft transfer an ft transfer call to to be like a hardcoded methods that we know

  20. 13:07

    and we said okay it seems like this is a fungible token transfer so this particular approval is not just a simple function call that uh requires one youtube year it actually going to transfer tokens to this particular account and if you see this you're like hmm maybe i shouldn't approve like transfer all my wrap here to the chats account so okay so so this is the idea for payable um

  21. 13:39

    so deposit works the following way you deposit the amount that you want to write we say this should be a positive deposit then we grab the predecessor we call a method internal deposit which increase the amount on your account that deposits to your inner account then we increase the total supply and we emit a lock saying deposit blah near to blah yes so what's an internal deposit uh we basically say give me account

  22. 14:34

    they give me current balance and then we call expect which saying if the account doesn't exist let's panic if the account is not registered the deposit will fail all this near deposit method will revert then we use overflow and check to add the balance in case there's some balance overflow which is unlikely for this contract but if you're implementing some other fungible token

  23. 15:04

    contract that will use this as a helper or like we poured it in your lip it's good to have this check here to provide a proper bank and then the insert account back this is the new balance one small arrest question over here what's the pub crate it's publicly on the current right yeah so create in this case means our module so this stuff so it's only going to be visible to this

  24. 15:35

    module but not exported the module being the contract module yes for like all the code in this codebase yeah all on src thank you so i have two helper methods here a third one yoktan it means so i do have a question though about the use of the crate is that just uh good practice because if you were to you know export this import this from another one you wouldn't see it uh because otherwise

  25. 16:08

    it's still hidden because there's no macro on the impul right yes but for example if you do this method uh that will be public so ah yes a wasn't compiled method will actually export this but if i will hide it from pop crates and make it fully private then for example other i see modules will not see it but i wanted this to be public but not fully public say that again it's public and what which context so it doesn't export this method from

  26. 16:47

    the contract towards the contract so if i will for example implement this like this then uh when i compile it towards wasm this is a public method that will export will be exported to to the webassembly contract so i will be able to call you to actually be able to call it one train yeah that's like a view method which means no it's a change method like every method is a change method by default it just

  27. 17:18

    you know like it's tricky so yeah i guess it would be so you can attach a deposit so but it doesn't change anything yeah still it saves some bites that we don't export it uh okay and uh now withdrawal works somewhat similarly we first assert one yoktan year so you need to attach exactly one yacht near to this method otherwise it would fail so we don't want to like you to attach more don't want to attach less otherwise it would be insecure

  28. 17:57

    now we do internal withdrawal so we decrease the amount from from your account we remove the total supply and we emitted look saying withdraw blah near from given account and then we issue a promise to this account to transfer the amount of new year you wanted to withdraw plus one yoctan year that you attach to deposit and we return this problem so that's a simple wrapped method that allows you to mint and

  29. 18:44

    and onward up with burn tokens to to receive near back so now let's go back to to a more complicated method which is a fungible token core apa so here's a trait trade is like an interface in other languages and that's a common trait it just defines defines methods but do not implement them so there is a four methods that required for core api so first one is change method called ft transfer it transfers the amount from a sender

  30. 19:30

    which is a predecessor to the receiver the given amount and you can put an optional memo json works the following way if you don't put this key like you don't have nema key then it will be option none and if you put a key memo then it will be options some so it's going to be some string um memory you can just use it to give some memo associated business transfer so for example like paying you for lunch and here's the memo

  31. 20:11

    now second on the blockchain yes it's public fit now ft transfer call uh it has an extra field called message and this this is similar to walt you you're basically gonna give this message to the receiver and the receiver will have to tell you how much fonts it don't want so like return them unused amount and that returns a promise now you have a total supply and a balance of an account so it returns zero if the account is not registered

  32. 21:00

    instead of panicking um this is a trade on a receiver side this method does not implement or this country does not implement it but it just defines f t on receipt on transfer so it gets us who send it the amount and the message and you need to return the unused so you need to return and use tokens that should be returned to the sender the idea is a unison for example only needs 450 tokens out of 500

  33. 21:41

    to account for slippage and then the rest 50 should be returned to the user if the price didn't slip in between finally this is a new sdk bug we have a resolver so this is a for the ourself for the callback and i also need this trade to actually implement it so right now i filed this uh issue on on github for new sdk is basically x contract is actually consuming this trade which

  34. 22:15

    means the trade stopped being a trade in our contract that's why i just copied it here because i also want to implement it and that's a callback so the way we implement the core api it didn't make sense by the way but it's the macro ext contract macro is is taking in the ast of the trait and then it's it's constructing something different and so that so then that's why you have to like you know do say it again so that then the other

  35. 22:53

    things that come after it will be able to refer to that trait um one's looking from the outside as a cross-contract call the other one's looking from the inside well so so i i suggest we refactor x contract call so it should introduce some kind of a macro a module or like a method whatever that will initialize this uh it will basically when you call it it will return you an instance that implements this trade so you keep the trade but you also

  36. 23:29

    have a new instance that all methods for this trade that will return the promise so it will be much nicer apa um i think i'll be able to work on this you're saying you can reduce it let's see one instance of the trade is that what you just said yes but you also will simplify uh this particular call like when you do a cross contract call it will be much better api that can be visible so as we discussed before you would do something maybe like this so you would say who you want to call in

  37. 24:01

    this case a receiver and then then you can do ft transfer and this ft transfer will actually be a trade implementing like on it this piece can return you an object uh an instance that implements this trait so when you call ft transfer you actually call in this particular method except this api will be different it will be a promise so maybe it's gonna affect it slightly or like it can wrap

  38. 24:42

    it into promise of value anyway we can figure out how to actually make it um make it work i'd like to restate why we have both of them up above if you only have the first one is just so you can do this cross contract call here ext fungible token receiver is that correct yes that's what the ext contract in like trait is for and then the second one is for lets you do what if you didn't have it what would break

  39. 25:14

    it it just uh the second one let me do the here i implementing it okay thanks so the implementation of this is actually these two methods are nice in terms of so i do assert one yokta then i do my input and then i issue internal transfer here i do the same the same for lines but i also doing the promises that i'm gonna call so i'm calling um a fungible token receiver to to get on transfer i'm passing sender id amount and a message

  40. 26:10

    then the receiver is receiver id not deposit and then attach enough gas all all the remaining guests to the receiver and i estimate that i'll consume 30 pure gas for this call well actually no um 30. well yeah for this call because i'm attaching five tera gas here and the rest will go here this call cost five this call cost five and this one then right now calls 10 so it's 20 for the promises and

  41. 26:58

    5 for this and 5 for our own now what we're doing is uh okay and two two other methods are fairly simple so total token ft total supply just returns total supply and balance off we get the account and say azer give me a balance of zero and internal transfer does the following it checks that sender is not a receiver then internal withdrawal internal deposit we say oh here is a transfer happened of this

  42. 27:49

    amount from here to here and if there was a memo we say minima of this and we actually wrap this memo so it's not just like a pure log that you can dump from the contract it needs to start this memo okay why does it matter that it's not a pure log um well if someone depends on this log on the log data to to prove that the loan is person then you can just say oh i did a transfer of million

  43. 28:30

    tokens and then you proved this that you did this transfer but you only transferred one token so if you rely on logs to prove something memo can fake it all right okay finally resolve transfer so this one fairly complicated like more complicated than used to the because we handle that the account can be deleted in during the ft call fk transfer call both accounts can be deleted the sender

  44. 29:12

    account can be deleted the receiver account can be deleted and the receiver also can try to like return some bad value to you it can also panic on this call and it can overspend the tokens that it has it cannot do a refund all right so here's what happens is we call promise results instead of handling it using a callback macro because callback micro will panic if the callback panics but we still want to do a refund

  45. 29:51

    then we do promise call so not ready doesn't exist um successful means the promise to return value has succeeded and failed means the promise has failed it means we should refund the entire amount so if it succeeded then we parse the value or try to parse the value because it can return me some fake data if it's non-parsible it's a misbehavior where we find the full amount if it's parsed then we return a minimum of amount and unused amount because

  46. 30:31

    let's say you can say oh i i got 10 but i will return you 20. that's not good so you can most return amount so we got the unused amount we want to refund if it's positive then we'll try to do refund so we try to get a receiver balance but receiver can be deleted in between it means the balance is zero for us it's not different when the receiver was deleted so it has a balance of zero so if receiver balance is positive then

  47. 31:12

    there is a chance we can do a refund so we calculate how much we can refund we'll try to refund full the maximum amount allowed so we will try to refund either receivers balance or then use balance whatever is available and need it then we say okay receiver now you return the refund amount um we do not need to use internal withdrawal here because we already verified the receiver balance

  48. 31:52

    is positive so account exists so we don't need to read this value again now the interesting part comes in when you're trying to say well where this um this refund should go so we check if the sender actually still exists so sender can actually also delete their account in between to to like try to break our contract like some other api we don't want this to happen so we want to handle it carefully so we check if the sender account exists here then we say okay

  49. 32:33

    that's a best case scenario boss account existed refund happened here's the balance so you got your sender balance plus the refund amount and we say refund of this amount from receiver to sender and we say how much money the sender actually spent so this is the amount minus the refund amount it handles this like corner cases nicely okay and if the sender was deleted um so there's two things we can do in this case we can

  50. 33:14

    say oh yeah the receiver you can keep the tokens don't need to refund it or we can instead actually burn it so in this case we decided to burn these tokens which is currently will be locked forever on this contract the year will be locked forever in this contract but uh in theory we can update the implementation of this contract so the owner of this contract can withdraw gas fees that it earns for the transaction for like for the good of the system

  51. 33:54

    so we say sender's account was deleted uh we decreased the total supply we say the account of the sender was deleted just to tell what's happening we burn this refund amount and if unused amount was not positive for receiver balance was not positive then we just refund you say the sender spent everything so there's no refunds so this is how the ft transfer call works it's optimistic that the receiver is properly implemented and the sender is not malicious

  52. 34:39

    and the receiver is not malicious but it still handles it properly never panics here no matter like what you do so transfer call always gonna return your value and it's not gonna panic unless you run out of gas you're saying it's optimistic these checks happen after the fact in the callback yeah yeah after you like called the receiver to say okay go like go wild with your deposit

  53. 35:22

    um and the receiver can essentially block this call by doing like a bunch of stuff and then only after this return value so it can like any state can happen in between okay um so this is a core fungible token api it's going to be similar for all fungible tokens and it's going to be part of the library that elias started to work on like near lip or something so you will be able to something like drop and netted

  54. 35:56

    or drop something like a pub fungible token on jubal token on your core contract structure and it will have all the logic there well somewhat like we still haven't solved that did that make it be nested under like functional token dot function name or will they be top level yeah yeah i think it's all domestic well meta is going to be public like as is because the interface but the value is going to be under this tangible

  55. 36:47

    token all right so we have 15 minutes uh let's quickly go through metadata first metadata right now i call the trade fungible token metadata provider it returns a functional token metadata with the code of ft metadata so currently it doesn't have a version maybe we should add a version here it's a top field in the json so so it would be something like conversion and you will say version of the metadata like meta version

  56. 37:34

    samsung and it means it follows the standard of this this metadata so if you have data standard later when you parse it you like okay which version that is ic and then you can pull all the necessary fields for this version so for example if icon link or like icon url doesn't exist then we move there i also asked to rename reference because ref is a keyboard and rust and it complains

  57. 38:13

    you can still fix it by doing surgery name i think you can do something like this and then do the name something like ref and i don't remember the exact syntax but it works somewhat like this and it will be fine but you can just call it a reference and don't worry about this so i used to give the generic name wrap near fungible token symbol w near and reference to the source code where it's located and 40

  58. 38:55

    24 decimals this yoctan year decimals number should you instead point to a release instead of a because master could change yeah good point um well i didn't push it sir it's kind of problematic to get my kind eat first and then yeah rabbit and then there's a chicken the egg can issue yeah yeah i guess you could make it a branch and then and then storage manager is essentially a generic interface or standard

  59. 39:47

    to manage a storage per user account on your contract and here's the interface it is can i ask a question about the metadata is there a way to update the metadata like will you be able to update it later after it's deployed not for this contract so it's hardcoded it's static okay yeah i mean ideally we would you could you know we could have publishing through a contract too yeah you can basically like pass it from

  60. 40:26

    self and then just give the reference for himself so when you initialize this you actually pull put the reference there so that way you can solve the chicken egg problem all right so storage manager it has four methods to to change methods and to view methods so storage deposits it works like deposit near for grappling year somewhat similarly you tell which account should receive it

  61. 41:04

    it's an optional field if it doesn't exist it means you want to deposit on your account the predecessor account and storage withdraw um tries to withdraw available amount from the storage balance and storage minimum balance is the minimum amount of near you need to provide for the storage to register your account so below this the contract will reject it because for example just to store the amount of storage you have so for this particular

  62. 41:42

    balance you need the account size um and finally a storage balance off gives you a balance of the allocated storage for the guild account it also doesn't panic we use the structure which provides two values total amount of balance allocated in year for this account and available for withdrawal so like this amount of year that is free and not used by this contract so let's look what happens to get more understanding science this contract is fixed size it doesn't need variable

  63. 42:28

    amount of tokens that actually enforces you when you deposit for something it's saying you need to attach exactly uh minimum balance storage medium balance um like this so we get how much you attached we verified that the storage minimum balance matches the amount you attached then we say okay what's the account we should deposit this to if it's a this essentially costs the valid account id to account id

  64. 43:17

    and if it's not provided then we do predecessors so your own account can you throw the uh storage minimum balance into the error message that's there for usability maybe but it requires like uh it will burst thrown from a certain queue a 30 q provides the values that it mismatched so it will say you gave did that is that um but you need to you need to give me so it will return this value as well

  65. 43:56

    when you unwrap the panic so you get the account we do an insert and then we verify if it's it was already a value there then you panic saying oh well the account was already registered okay already registered take your money back um if it so we we do not work as a standard expects like you cannot increase the balance above certain value and then because the balance became your balance is zero you can actually withdraw it so the only way to withdraw storage is when your

  66. 44:43

    token balance is your it requires you to drain your account first and year um so your total becomes amount and available becomes amount when you receive some tokens or deposit near then the balances changes so if your account exists it exists you have some balance then we say the total amount of the balance you have is a minimum balance but the available balance is either zero if the balance is positive

  67. 45:39

    which means you cannot withdraw storage because you have some positive near account balance or corrupt near token or if your balance is zero it means you can safely delete your account so your storage is available now if your account doesn't exist we say your total amount at zero and available amount is zero um okay let's look at the storage withdrawal can we look at that one more time yeah

  68. 46:30

    available if balance is greater than zero then it's zero so this is a token balance not a storage balance if you're talking balance your token balance is positive it means you cannot withdraw your storage deposit so you're this is what's available for you to withdraw yes okay it's like available storage so let's say you have like one kilobyte of storage and you used 200 kilobytes or 200 bytes

  69. 47:11

    then available will be 800 for the normal case in our case we used full amount when your balance became positive and we say you cannot withdraw the storage make sense you probably need to get more context from the nap so this is snap 145 that talks about this and that explains the logic behind it yeah i'll take a look at that yeah and now withdrawal works the following way uh you say

  70. 48:01

    how much storage you want to withdraw like how much amount of near you want to withdraw from the storage balance and well that's actually back oh no it's not a bug so the storage withdraw also requires you to issue um so every single deal is near or the positive value we say this is a security method all right then we need to say it'll attach at least one yacht in here we say how much balance you want to withdraw and the only way to withdraw is entire

  71. 48:50

    balance because we don't keep fractions so you say in the withdrawal amount should be exact storage minimum then why even have the argument uh because the standard is not generic so it's not only for fungible token and we get your account we check well we actually delete your account and then if the balance was passed positive will be rewarded by panicking saying hey your account has positive token balance which means you cannot close your account

  72. 49:32

    otherwise if your account existed but the balance was zero which is what you hope for when you do this then we return you amount plus one yoctan year that you've touched and to your return now the balance is zero zero and if you don't have account you cannot withdraw this so that's withdrawn so that's like a subset of the api that provides so yeah we basically looked at the three standards so core api

  73. 50:22

    metadata api and storage manager and that's it so for this token i don't have tests yet this all tests they probably gonna fail because of different api that was there and i want to use simulation tests instead so i will keep it for now like this in draft until but we can actually test it and test that eventually awesome when are you thinking this will be published what exactly this contract or like

  74. 51:07

    contract with simulation tests yeah like on on github for people to reference so i have the repo published already i just need to add um simulation tests and maybe is it in the core contracts repo or the uh yes it's a core contract so just my local repository is still initial contracts but yeah it's in core contracts i removed old wrapped near she moved it to nep 21 and this one was

  75. 51:43

    currently 141 we still don't have a version yeah full full functional token name that you'll give samsung 1.0 um yeah let's wait until we can actually publish a new one then remove some boilerplate maybe move all this logic into near lips it's also much simpler where's near lib live um i i'm not sure it's currently a branch holy separate sdk but it might move to a different crate or different repo

  76. 52:28

    you said ilya is working on that yeah he started this and that's it's like i'm imagining it sounds similar or like it has some similar functionality to something like uh open zeppelin where you can import yeah yeah fantastic i love it but rest specific not or will it be like raw wasm yeah i mean ideally if we if we you know embrace interface types that could become you know broad but

  77. 53:06

    but until you can like import a rust into assembly script then no i mean it's still great yeah the assemblies assembly script uses slightly different uh storage model it uses jsons for balances and this one uses bars for balances so they're like different in terms of what it produces so okay um thanks everyone for listening i'll stop