Skip to content
by Unity Nodes
NEAR Protocol

NEAR Live Contract Review | Episode 18: ref-finance

about 58:11 long

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

About this video

We've reviewed a `ref-finance` contracts that implements an exchange with support of multiple fungible token pools that is working in a async NEAR environment.

During this review we've found a few issues related to storage management on liquidity providers. As well as an issue of losing (locking in the contract) of fungible tokens during adding liquidity.

Source code: https://github.com/ref-finance/ref-co...

Docs: https://ref-finance.gitbook.io/ref-fi...

Episode #17:    • NEAR Live Contract Review | Episode 17: NF...

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

4,917 words

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

  1. 0:01

    hey uh this is eugene and we're here today with chad and mike and today we're gonna record another episode of live contract reviews today's contract is ref finance this is an exchange contract this that extends on top of unislope and also aligns with the near asynchronous ecosystem to solve certain problems that arrive when you have asynchronous transactions so let's jump into this it's currently deployed on the mainnet but it doesn't have a ui

  2. 0:44

    you can only interact it with this with cli so far and it's open source and you can see um and play this so to start we start in this leap i just updated cargo tunnel to the latest third version of newer sdk and this is the commit from current master so some stuff might not work as expected but hopefully we can resolve this quickly so lib we set up small lock via lock using a macro and this is a main contract structure

  3. 1:43

    using panic on default all right so we have owner id the account of the owner of this exchange that has a extra method to call exchange fee is a fee that exchange keeps to itself a referral fee that is given if someone is trading using a custom front-end so the front-end author can keep a small fee by passing their own referral referral to to every trade action or every swap action and pool is a list of pools i'll go into this deposited amounts is

  4. 2:34

    the account to account deposit and we'll talk about this and white listed tokens is a list of token ids a contract ids that are allowed to to be exchanged in this contract first what is a pool the pool is an enumerator so it's a generic pool wrapper that can be implemented in multiple ways so so far it only has one implementation which called simplepool which is a pair of tokens similar to unislope that exchanges

  5. 3:23

    and it currently has max number of tokens equal to we'll go into simple pull later so pools may have different implementations in the future and there can be multiple different types of pools they are in vectors which means once they added they cannot be removed which is interesting but probably a safer way i prefer to use a lookup map now for instead of vector because vector doesn't have

  6. 3:53

    ability to delete but sometimes it's preferred to use a vector so deposited amounts is accounted you to uh deposit and what scenario does that apply sorry that's for uh that's like a ui that that comes through on the ui right like once a pool is set up between two tokens it can't be deleted or is this for like contract implementation um well no no so let's say in the future we have some legacy pools that we don't

  7. 4:33

    need anymore then we cannot really remove them from storage uh remove the ability to interact with them because they're in vector that would be between like banana tokens and wrapped no more like let's say the simple pool has a bug or something like this so if you want to disable all simple pull issues uh they will be in the vector forever because we cannot have empty slots in the vector okay so it is it's like a contract upgradability consideration not

  8. 5:11

    uh like what people see when they use the app i guess i don't know so account deposits is information per account so we have a native amount so this is used for storage right now of this given account and and there has we also have tokens which is a hash map from a contract id to a balance and what's interesting is account is can have different tokens from the master contract so this is

  9. 6:02

    somewhat was unexpected but we can see later how that works because i would think what's the point of store an account or if it stored token that's not that's not cannot be traded because it's not whitelisted but maybe you can't even add this then then you can use an index but maybe you delisted talking that then you can only withdraw it so i don't know anyway so it's a hashmap not an ordered map so you can have only limited number of

  10. 6:41

    tokens on your account at a given time it's just another choice unclear why you want to have it this way but maybe to be efficient in a number of swaps you want to have it this way but can be properly implemented later to to be an ordered map minimum account deposit length is is 16 plus four so this is account id plus balance and it's a storage for i guess for depositing into exchange let's go

  11. 7:29

    back for now here so when you initialize a contract you give prefixes and set up exchange and known already so um you can use into at least in this case because you don't need a valid account id now a payable method to add a simple pull this can be called by anyone apparently let's see and you need to pay for the storage so you you create a simple pull uh that is index at the length of the pools

  12. 8:30

    the token we check that there is no talking duplicates okay we set a fee equal to feed that you pass plus the exchange fee plus the referral fee and you pass exchange fee and a referral fee i mean okay so the fee is measured in percentages of basis points i think so for example 30 is 30 basis points now we have let's look at the add internal pool so what it does we remember the storage before adding to the pool

  13. 9:32

    id we already got the id in the pool so we don't really need this id and we check what's the difference in storage multiplied by using and byte cost which is already updated and we make sure that there's enough storage then we generate a refund and we transfer it to predecessor and return the id cool okay um simple new let's see what happens when we create a simple pull so total fee should be less than vd

  14. 10:29

    wiser which is 100 and total of the exchange should be less than total fee then number of accounts should not be one but also number of accounts should be less than maximum number of tokens or equal and then we convert it into a vector of accounts then amounts volumes swap volumes with a swap volume it has um input and output for every token we have input and output okay fees shares

  15. 11:40

    right so so we create a unique prefix for for this i was thinking to to make an example eventually it's uh out of scope for this contract of how you manage storage so i see pattern where we had to use a unique prefix and we come up with this idea so like oh let's use s here well let's use uh something else there and then you need to remember what prefixes you already used for example

  16. 12:20

    here you use p g and w you need to make sure you didn't use s in the past so i was thinking so i'll suggested to use um kind of a unique id for for creating a prefix my current thinking for this it should be something like you do uh state key and then you may have something like oh or cools deposited amounts my cpu is going wild i don't know what's going on it's slow one second i'll quickly check what's going on

  17. 13:35

    at this c line so maybe just being weird so you have white listed tokens and then you have now we need a unique prefix for simple pull you do something like id and what you need to do next is just to put borsch serialize on it and that's it so now when you use a prefix here you can use state key colon colon pools try to vic or up that's it so you just created a key that is unique

  18. 14:36

    for this particular vector now for simple pull you would do something like this you will create a simple pull and you pass id and do the same you just do try to back it created a unique prefix for a simple pull and this is you reuse it across entire contract so it's guaranteed to not repeat itself because enumeration starts from like zero then it go one two three and then the ig gonna be unique resist using this and

  19. 15:20

    well how long of a prefix would that be uh this is one byte wow and this is gonna be five bytes so it's exactly the same as like except so it's also efficient like it's binary plus you cannot like mess it up with um nearby june so what you need to be careful is um is nearby and default key so default key is state so if you have a prefix that starts with capital s then you can have a conflict if it's like four characters after this

  20. 16:16

    this state so what is evgeny capuan suggested he suggested that default key should be this it should be nothing so it's basically empty key will be a state of the contract and that you cannot have a conflict because for empty key you wouldn't create cool anyway this was a bit off to to talk about like how to better manage state keys prefixes okay we finish maybe we didn't finish

  21. 17:01

    simple pull i think we did yes so shares is a liquidity provider balances okay swap and you can pass a referral id so this is the account id that will get a referral fee which is can be your front end where you get referrality so when you pass a swap if you pass a referral id then you will get an extra one fee i wonder what happens if you don't pass a referral id so let's say i'm doing my own swap from the command line and i don't pass it will i actually pay

  22. 17:58

    more to the exchange or something like this anyway it requires one yoctaneer getting the sender ids predecessor it takes a list of actions so vector of actions and vector the action is is it takes a pull then you say which contract you're swapping like which token for fungible talking you're swapping from uh how much near you want to put if none it will take amount out from the previous step and will fail if amount is none in the first step so you kind of can

  23. 18:43

    create a chain of swaps to like arb the pool itself and like keep the money so you can rebalance the pool if you have a circular dependency there or you can like swap one token to another through a set of pulls by paying ton of commission obviously talking in talking out is another account id and the minimum amount out is how much you want to get from the swap this is how you define slippage cool

  24. 19:27

    all right so let's see what happens the previous amount is none then we're gonna go through the list of actions and and so that's interesting because my i don't know if you probably see that it's optional string even so as ref returns you account id but after a clone account id becomes a string so i wonder if you can just do into well now it figured out that this is an account id even so it doesn't matter in this case

  25. 20:19

    maybe it will not figure out what is the into i'm doing it because um actually no it knows okay um so first action should be should have the amount in but the next action doesn't need to then we're doing a swap that returns a previous amount it kinda gonna fail if list of actions is empty it cannot try to honor up here and this will panic which is good at least we cover that that corner case

  26. 21:08

    so we call internal swap pull in talking in a mountain talking now mean amount out sender id and a referral why clone referral here you can just use this ah not gonna change it during the review but i can put a note okay let's actually see what is internal swap internal swap

  27. 22:10

    deposits we get the deposits of the sender id and wrap a default i mean if account doesn't exist we can just panic um the mountain so we have sub let's sub takes account id which it doesn't really need a reference to need it here i see so we need a token we check that the value is higher and then we subtract the value

  28. 23:16

    and reinsert the token as a hashmap maybe that's supposed to assume that it's not a hashmap or what's the particular reason of doing it this way because you can do you can do self tokens entry then you say okay and then you can do i actually don't know there's probably a method something like

  29. 24:21

    oh yeah you can do and modify and pass a function so it's going to be value and you can say value plus equal well you need to check but you can do well yeah you need to like do this kind of logic here so you need to do this and then you do minus equal to amount and and modify returns you self occupy to backhand um and then you can say uh this whole entries thing we're doing

  30. 25:23

    this is for hash map right can we do the same thing for rust collections that we have currently near could be cool right yeah that would be very support but it would be cool to have something like this how you actually you what you want to do is you want to enforce that this is occupied so there should be something like a method like expect or something i cannot find it i need to look into this later so if you go here and then go to

  31. 26:13

    entry um it does have a answer to this but i wonder how you actually as enforce it there should be some like matches occupied entry or something or whatever well we can figure it out later but anyway i think i wrote this uh first to make sure it it looks like an order it map and you can use it as an ordered map

  32. 27:14

    or like a lookup map per account and this way you can have more tokens and this is going to work so you need to reinsert it correctly okay going back where we were into deposit so we removed the amount is there any reason for a mount in to take a capital u 128 here an internal slope yeah yeah it doesn't doesn't have to be it can be a small so you don't need these conversions pull and you get a pull by index

  33. 28:08

    and then you execute swap in a pool which is a generic implementation that will do i can like pull specific action then you add a deposit on amount out for the token that you get um kind of didn't check that the amount in is different from amount out or talking in different from talking out then you insert your deposits back for the given sender replace the pull right because it's a vector

  34. 28:52

    and return the amount so let's see what's going on in the swap so a simple pool called simple swap and what we do we get token index talking in talking out internal get return we still haven't checked that the tokens are different token index is situated on a pool on a deck and just gives you what would the is there an exploit i can do by swapping like banana probably checks it somewhere

  35. 29:37

    what would happen if they were the same seems like i would just burn gas fees and get no benefit no it can be like overflow somewhere here so like you can mess it up somewhere eternal get return so it returns you the value that you should get oh yeah it checks it here so so you out of luck amount in multiplied by phi multiplied by out balance divided by v by imbalance plus

  36. 30:43

    amount this fee what's going on here what is a mountain and imbalance well that's a balance of the pool total balance of the pool v minus total fee it's probably like a unison formula that like optimized this commission like fees into the calculations um balance then right yeah it's probably just unislope so this at least

  37. 31:52

    one okay anyway so you calculated how much you would get out for the given amount that you put in out after the fees and then we check that you you should get at least this amount just a small note that we should probably put the end vlog after that assert right yeah it's interesting because i'm not sure how ethereum does this but like let's say a transaction was reverted and we throw a bunch of logs

  38. 32:40

    did we did we log we do log but like question is like is it considered to be real or not anyway so mean amount previous like if someone is indexing and reads that log what is the invariant here integer square root so we multiply this by that all right is this what is that and then and then we did this swap and we verified the invariant that new invariant has to be higher okay why we need a security

  39. 33:30

    is this like the the pool has to have over collateral or over liquidity or something i'm not sure i'm not sure i'm just kind of guessing shares total supply that's a numerator it's how much fees you got in square root by total supply and then it if it see more then we'll start giving fees away and we minted shares to exchange right we give some shares to it as a liquidity to exchange then

  40. 34:12

    we give shares to referral but it doesn't check for the storage when you give liquidity to referral so you can abuse it's through referral by like having unregistered referrals spamming in and that will create a ton of storage as maintain shares to referrals so you when you do mean shares you need to make sure that this account that you adding shares to is already pre-registered

  41. 35:07

    so like somewhere earlier in this stack if i provide an a referral that is not yet registered through an error yeah and we need to maybe charge it somewhere on the referral account so this has to do some storage management that was not done so this needs more explicit storage management on the account level is it uh min equals max storage management like fungible token no no no it needs to be flexible because you can have liquidity on every pool and you supposed

  42. 35:57

    to in theory have multiple tokens so right now they're charged from the account balance anyway um we minted shares total fee divided by referral fee what that's a denominator so numerator is how many shares we give and divided by new invariant and total fee and multiplied by referral fee okay got it cool uh yeah that should be fine okay let's see quickly check mint shares so if shares zero we don't give any shares otherwise we meet new supply

  43. 36:59

    and we get the go into collection to add to previous value plus value on a given key and reinsert this to the lookup map so this kind of entry that we were talking about let's also quickly check integer square root so we add plus one divided by two and then we use newton method i guess divide by two until guess is less than the rest

  44. 38:02

    wait oh right yeah until it's like convergent okay i think it works alright more more back and then we update volume of in token in and out that kind kinda strange to me shouldn't we update out out put oh yes seems like it because x input and index output

  45. 39:12

    volume the output of uh given token yeah i think it should be like this let's try to do it's so slow for me is the volume of the volume of the liquidity in there uh this is just for statistics like how much it was swapped that's how i approach okay i i'm not quite sure this is the correct way of maintaining

  46. 40:17

    fees but probably iliac checked this mess before so this makes sense to to do because you didn't mint more shares to the existing shareholders but you deleted the shareholders and you haven't used it genomic you haven't used the total percentage so you divide it by total fee but what you should have divided is you you it it's missing it missing fee denominator so if v like total e is 30 and exchange fee let's say

  47. 41:25

    is five then this one is let's say we minted one let's say the numerator is one the new invariant let's say is one new in a new invariant is let's say 10 000 like maybe 100 to 1. so we minted like one percent in in during the swap oh maybe it's here yeah there's a feed divisor

  48. 42:33

    yeah i see okay allowing to provide fee in what's bps stand for in this case um basis points so it's like one set one hundredths of a percent but then yes the numerator already reflects uh the feed so so it's already took point o three let's say like whatever percent out so so it did increase by some amount still this is all right maybe there's some formula that's explained somewhere in the mass but it seems plausible that this works

  49. 43:40

    okay let's go back to the original so once you've done the swap you get amount out and you updated all amounts and and you finished swap so now adding liquidity and removing liquidity um we don't have much time so adding liquidity you need one yokta a predecessor is and you we get a pool we get deposits for the given account we get tokens we remove deposits uh what is this

  50. 44:41

    it's iterator okay um so we remove deposits from your account for the given tokens and then we say we add in this liquidity and we save your deposits and we save this pool okay let's see add liquidity pull and liquidity it should match shares if pull is initialized to fair supply okay

  51. 45:45

    we go through all tokens make sure you add in non-zero liquidity of every token and then uh the fair liquidity gonna be this amount multiplied by number of shares total supply and divided by this amount and it calculates a minimum now we multiply it by this to calculate how many

  52. 46:52

    what the increment the amounts or internal amounts by the amount you add it a little hard to follow i think comments would go a long way i also don't pretend to know how this all works this is but you don't also really find the amounts that's a little off so we charge the full amount but we didn't refund the full amount so if you over attached the amount we will like take it from you and keep it but give you little liquidity

  53. 47:45

    so that the amount's going to be consumed like they're going to be lost they're not even consumed you mean so this is amount in so this is amounts in and this is self dot amounts is the amount of the given pool so there's two tokens but that's fine we calculate how much how many shares you should get as a minimum of every amount that you add but then we only add this amounts to the pool based on the minimum amount you provided

  54. 48:36

    so we this amount of token we used what we didn't do is like say you provided a ton of token one and only one token two well we only used like both of them by one now ton of token ones are lost so this method should not only return the fair supply it should also take all the amounts you provided and refund them back to your account so this is what we don't do so basically you should refund maybe you just do this right you can do

  55. 49:30

    mute amounts and then you do amounts i minus equal amount as u64 and here you say i equals to zero so this is what you need to do and then you need to say uh here's shares and amounts so you need to return or you can take it by reference it's even better now in the the one that called it it also has to

  56. 50:36

    too far too close i also should take a mute and here we give good amount and then we need to do reverse i guess not here but so this would be fair because we will return this tokens that we didn't took what is global oh never mind it'd be really interesting to dive into

  57. 51:38

    the balancer contracts i know that like what i've added liquidity to balancer and you can add like basically like the exact amounts you know like if it's 50 50 and you can say like okay i have exactly 50 like wrapped eath and then like 50 die or something like that or if you have less of one then it does some kind of magic to make it work but i think it's like a different call and you'll have

  58. 52:02

    much more slippage it's missing mint shares and then she should be here because right now you can do front run pretty badly on this this action okay uh we're out of time but let's quickly go through remove liquidity and see what's going on here so remove liquidity again gets the amount same way except for minimal amounts

  59. 53:05

    pull replace pull um yeah that that actually is safer method because well because you provide this mean amount for every amount you want to get so you get shares amount this is how you're getting how much you're getting so it should be higher than min amount and we remove it from the pool and push the result when if it was all shares we also removed the you from the list otherwise we say how many shares left on your account to decrease the amount of shares and return you

  60. 54:01

    a vector of amounts that you've got okay this is good and then you get the amounts we just re-insert it back to you um and that's it so we store your account cool uh overall i think we mostly covered the basic logic here i can quickly look what's also there storage management it kind of does something so i don't know we don't need to dive into this and it probably ignores four switches okay talking receiver so this one ignores the

  61. 55:03

    message i like it it expects the message to be empty i would prefer message would say deposit instead of being empty then turn on deposit it adds tokens and it so yeah when you deposit the token it has to be whitelisted in this case in theory we can map it from being an account id to to be an index because we have a list of white listed tokens so we can save a lot of bytes by just having use 32 index of tokens that are accepted by the account

  62. 55:48

    by the exchange utils some utils we use so this vehicles that just gives you a bunch of data like pagination so this this is so common i feel like having a range would be beneficial fee volumes you mean in sdk rs yeah in for vector and like an ordinary map

  63. 56:51

    and a bunch of calls i think view methods are not that critical to look right now but yeah overall i think yeah we we can't find anything critical except for liquidities can be from trent so when you add the liquidity you were not guaranteed to to get enough shares and also not guaranteed to to get a refund on your amount so if you see a transaction in the pool and you can't frontrun it as a validator you can basically

  64. 57:32

    send a swap transaction in front of it then the liquidity will be minted at like very unfavorable conditions and you like swap it back in the next transaction and that way you you pretty much took entire ammo well not really but you can take a large amount because they will get one share for losing all amounts the problem is this amounts are going to be lost because they are not not really going anywhere not going to the pool

  65. 58:11

    okay um i think that's it for the contract today so thanks everybody bye