Rippled: Support for circular payments

Created on 30 Aug 2015  Â·  41Comments  Â·  Source: ripple/rippled

I think that in Ripple network the circular payments are a mandatory tool (arbitrage and other usages).
Circular payments are when the source and destination currencies are the same IOU but with a path that is not empty. Example: USD.Bitastamp-->XRP-->BTC.bitstamp-->USD.Bitstamp

Feature Request

Most helpful comment

It's not an uncommon use case, rogue5HnPRSszD9CWGSUz8UGHMVwSSKF6 has submitted 768,770 such circular payments, with the workaround of partitioning the circuits into multiple payments and offers.

All 41 comments

I'm strongly against such a feature

Disadvantages: removes liquidity.
Advantages: useful to re-equilibrate the markets.

But sooner or later people will find the unbalanced markets and will remove the liquidity, with or without this feature. It is already possible to do such transaction with the "trick" of sending two transactions with the path split (with some more risks).

@vinniefalco - Why? It only means that there will be 2 transactions sent instead of a single one as a workaround in case such a path is desired.

It seems to be an uncommon use-case.

If I want to be snarky, rippled itself seems to be an uncommon use-case. :-P

Right now it is an impossible use case (circular transactions are not accepted by rippled), actually uncommon features are for example the freeze flag, which rarely ever has been used to date.
https://forum.ripple.com/viewtopic.php?p=63362 states that this is a planned feature.

David looks like he's for it (although, I don't know if he's changed his mind). We don't always agree.

It's not an uncommon use case, rogue5HnPRSszD9CWGSUz8UGHMVwSSKF6 has submitted 768,770 such circular payments, with the workaround of partitioning the circuits into multiple payments and offers.

ahaha but I think me and you were the only crazy one doing this...I gave up because of bad fees in the payment engine, but now that should be fixed. Also I gave up because of your bot :)

@tuloski rEiUs9rEiGHmpaprkYDNyXnJYg4ANxWLy9 is also doing circular payments by mirroring all the circuits that rogue5HnPRSszD9CWGSUz8UGHMVwSSKF6 generates, so maybe that makes three of us :-) Other people have requested it previously as well.

This feature would be useful as there would no longer be a need to trust the IOU's of the intermediate legs, i.e. the last IOU of each path. Tangentially, it seems arbitrary that payments to the same account require that TrustSet limit is set anyway; OfferCreates don't require it. Also, it could reduce payment transaction volume.

For this feature to be useful, the pathset limits would need to be altered. Currently you can have 4 sets of 8. It would be preferable if the sum of all pathsets was the limit i.e. you can have a single path of length 32. The longest profitable path I've ever found was 25 steps in length.

Thanks for the tip about mirroring rogue ;)

The longest profitable path I've ever found was 25 steps in length.

Wow!!! I had no idea

Dogfòod mmmmm

Deferred credits makes most circular paths work. However, if a path tries to use the same limited liquidity twice, it will stall and exclude itself from the rest of the payment. That can result in very sub-optimal payment execution.

The payment engine must run each payment path backwards and then forwards. This is unavoidable. If two hops in the path attempts to use the same liquidity (for example, an XRP->BTC offer and an XRP->USD offer from the same owner) then which one succeeds and which one fails can depend on the order in which they are examined, causing the path to produce a different result in the forward direction as in the reverse.

We could allow circular paths with no limits; however, such paths could execute in horribly suboptimal ways as a stalled path removes itself from consideration. Also, this can result in payments that examine large numbers of orders and only consume a very small number of them.

On balance, I don't think they should be allowed. However, we could do the following:
1) Disallow circular paths (tem error) unless a "circular paths allowed" flag is set. This protects people from accidentally getting suboptimal payment executions.
2) Require a higher fee if the flag is set. This compensates the network from the higher work required.
3) Explain to people that if they set this flag, their payments are more likely to tec or to get bad rates and it's only for people who know what they're doing.

I'm not sure it's possible to justify this effort though.

I still don't get (after hearing it 5-6 times) the forward-backward thing.
Isn't enough to run the payment either forward or backward? Then if the same liquidity is used in a previous hop, and there is not enough in another hop, the second hop fails, so as the transaction. I see that for partial payments that could become complicated but still there is a solution to the problem...or not?

I don't get why if I can compute if the liquidity is enough to succeed, the payment engine cannot...?!

BTW I see the unjustified effort.

Let me try to explain it again as clearly as I can:

Say you submit a payment with three paths that use order books. Each path will start at a certain exchange rate and that rate will change as the path goes deeper into the order book. The payment engine will execute each path until its exchange rate changes, then really use the one that provided the best exchange rate. Then it will repeat the process getting worse and worse exchange rates until the payment completes.

Now, think about when it executes the first path. Say the payment is trying to deliver $100 and can spend up to 1,000 XRP. The engine has no idea how much money to put in nor how much will come out. There are two ways to do this and they both have the same issue. I'll explain the method the engine actually uses and you can reason out why the other way has the same problem.

The engine assumes that the full $100 will come out of the path. And it begins executing the path in reverse. Say the last hop takes the full $100 and needs .1 BTC to do it. So fine, the engine tries to make 0.1 BTC come out of the previous step. But say that step is an order book with only one offer at the best rate and that offer only outputs 0.05 BTC. That's fine, the engine propagates the 0.05 BTC to the previous step, but we now know that $100 is not going to come out.

When this execution finishes, the engine now knows that to run this path for one increment it must put, say $12 in. But it has no idea how much will come out, just that it's $100 or less. So it must run the path forwards putting the $12 in.

Now, say it runs the path forwards to try to put the $12 in. But say when it gets to that order book running forwards, that order is gone because a step that's earlier going forwards consumed it. What can the engine do other than stop using this path?

In summary:

1) When it starts executing a path, the engine has no idea how much to put in nor how much will come out.
2) To determine how much to put in, it runs the path in reverse. Unless this is the last increment of the payment, it cannot use these results.
3) The engine now knows what to put in, so it can run the path forwards and produce the final results. However, if the path does not execute the same as it did backwards, the payment engine is jammed.

You can't just ignore the jam or work through it. For example, the last increment of a non-partial payment must deliver the precise remaining amount.

Ok, it is gonna take time to digest it...but I like this kind of riddles.
Tomorrow I'll answer and try to make a counterexample and my ideas.

BTW I don't understand the 12$...wasn't the input XRP? I got lost at that point...and what do you mean for one increment?

I'd imagine most users of circular payments would only include a single pathset because they know that it will be profitable, so the situation of a pathset conflicting with liquidity consumed by another pathset should not arise. The simple way to enforce this is that a payment where the amount and sendmax are the same IOU, or are both XRP, must have only one pathset.

@tuloski I might not have kept the currencies straight. I should have done it on paper to be 100% sure to be consistent. But the point is, the engine runs the path backwards (possibly limiting at each step) to find out how much currency to put in. Then it runs the path forwards to see what actually comes out. If any step in the forward execution is unable to take the amount of currency that comes into it from the previous step, the path jams. If the amount that comes out is not the expected amount out, again the path jams.

If two hops in the path compete for the same limited liquidity, then the path will not produce the same results forwards and backwards. This makes it impossible for the engine to successfully execute the path. The path will jam, and the engine will stop using that path.

@donovanhide The issue is not conflicts with other paths. That's not an issue because the engine only runs one path at at time. The issue is a path that conflicts with itself. For example, consider a path that visits the same order book twice. Or consider a path that visits two orders books that both pay out XRP and the tip offer in both books comes from the same person who can only afford to pay out on one of them. If a path does not execute the same forwards as backwards, the engine cannot execute that path and it will jam.

Imagine a path that does BTC->XRP->USD->XRP
Now imagine the tip offer in the BTC->XRP order book is owned by the same account as the tip offer in the USD->XRP order book. When running the path backwards, the USD->XRP offer is funded and the BTC->XRP offer is not. Running the path forwards, the BTC->XRP offer is funded and the USD->XRP offer is not.

The simplest way to prevent people from accidentally causing themselves pain is to prohibit such paths. Otherwise, non-jammed paths may allow the payment to execute but cause the resulting rate to be very poor.

@JoelKatz I've never actually created a path that visits an orderbook twice, not sure why you would ever want to do that as calculating the input and output liquidity and therefore profitability would be equally as much of a nightmare for the arbitrage path-finder as it would be for rippled. Simple solution is just to limit the number of visits to an individual orderbook in a path (not pathset, sorry for previous confusion) to one and the number of visits to XRP to be one. Are there any actual examples in the ledger history of payments with paths which exceed this limit? Is this an issue due to auto-bridging always trying to route via XRP?
Currently you can't use rippling liquidity of a single account more than once in a path anyway, so there is existing validation already present with similar characteristics.

Probably should be clearer... I've never created a path that uses the same IOU twice, so I never encounter an unfunded offer which wasn't funded at the start of the state for a ledger. This whole feature request probably appears to be more an argument for not having unfunded offers, but that's a whole different conversation :-)

@donovanhide The problem can occur even with a path that doesn't visit the same orderbook twice. It can occur with any path that visits the same asset twice. The simplest solution to avoiding unexpected results is to prohibit such paths.

So, I try to make a complete example of the WORST case: partial payment, and same IOU in two hops (BTC in this case), and we suppose that BTC are only provided by 1 liquidity provider, such that if consumed in one hop, there are less or there aren't for the other hop.
Let's suppose I want to deliver 1000 XRP for 900 XRP using this path:

XRP--BTC--USD--CNY--BTC--JPY--XRP (BTC are twice in the path...this could happen only for a path of minimum 6 hops).

Doing it in reverse:

  • To deliver 1000 XRP I need 100 JPY
  • To have 100 JPY I need 0.1 BTC, the liquidity provider has only 0.15, so after this hop there are left 0.05BTC
  • To have 0.1 BTC I need 500 CNY
  • To have 500 CNY I need 10 USD
  • To have 10 USD I need 0.1 BTC --> here is the problem, since the liquidity provider has only 0.05, but I don't see the problem.
    At this point there are various options: I can consider that only 0.05 BTC are used, so that the payment cannot deliver anymore all the 1000 XRP, but only 500XRP (or the equivalent calculating the correct quality forward).
    This case is suboptimal, but it solves the problem.
  • Finally to have 0.05 BTC USD I need to put in 450XRP
  • I have the complete payment: 450XRP-->0.05BTC-->5USD-->250CNY-->0.05BTC-->50JPY-->500XRP
  • If the payment was not partial, then it has to fail.

I see it is not optimal, since only 0.1 BTC were used instead of the full 0.15BTC, but with other ways it could be optimized (split the liquidity for the two hops, recursively,...).
Probably splitting the liquidity in the hops is the optimal solution, but I need to prove it.
Also I think the optimization problem is convex, so that any optimization tool should find the unique maximum/minimum.

BTW in a more general case: why just forbid transactions with different forward-backward results, but allow circular payments? Not all circular payments will cause that behavior. Isn't it? It would require zero work.

@tuloski The situation where you would revisit the same IOU twice in a path would mean you have found a high speed solution to the generalized network max flow problem. That is flow-generating loops within the main circuit. If you have done such a thing, you should probably be trading on a real world forex platform, as you would be making a fortune :-)

https://www.cs.princeton.edu/~wayne/papers/gain-scaling_talk.pdf

I honestly can't imagine anyone is doing this on the Ripple network (I would have spotted it :-)). The simple case of non-revisiting IOU paths is more than suitable for your and my use case. Recursive application of Bellman Ford, using the negated logarithm of the exchange rates allows you to find a set of profitable paths, where you remove part or wholly consumed offers or rippling liquidity from the graph on each pass. Theoretically they might not be the most optimal combination, but they are profitable and can be found in a matter of tens of milliseconds...

@donovanhide Personally I wasn't visiting the same IOU twice or more, but we have to think at what rippled should do in that case...not only thinking about realistic paths for arbitrage. Also the example of Joel BTC->XRP->USD->XRP-->BTC is not good for arbitrage, because you'd always have quality < 1, but still a path like that could be sent in a payment in RCL.

I'll think about the generalized max flow problem ;)...I might have a new business

Simplest feature request would be: if Amount and SendMax are the same currency, each path in the pathset must not contain the same currency and issuer pair more than once. No need for a flag as the circularity is implied. Extend the limits for path elements to be the sum of all path lengths, and this number must be less than or equal to 32.

I am enjoying this discussion, but can someone explain why anyone would ever send a payment of this form (B¹ and B² are the same asset)?
A → … → B¹ → … → B² → … → A

Looking at the internal loop B¹ → … → B², there are only two basic cases:

  • It is not profitable, so it can be collapsed into B;
  • It is profitable, and the flows of B¹ → … → B² and B² → … → A → … → B¹ are different; in this case it's more profitable to split the two loops.

To me the sensible default should be: internal loops are disallowed (or automatically collapsed).

@robcat because the steps between B¹ and B² result in an increase in the holding of that asset! Given that the arbitrageur probably has a base asset that he/she wants to increase their holding of, which is not B (probably XRP given the current state of the orderbook pairs) any "flow-generating" loop which allows an increase in the in/out ratio of a long payment path is beneficial. However, max flow in generalized networks is a hard problem, so in reality, you'd tend to dismiss those paths and go for the paths without those additional loops.

Great Superbowl by the way!

@donovanhide Ok, understood!

Just to make clear how far should circular payments could go, let me propose an example:

There is loop of trustlines between three branches of a bank, such that is possible for each of them to make a circular payment in the right direction and profit (due to the quality in/out parameters). These branches are completely disconnected from the graph otherwise, because they have decided to use RCL just to use it to settle their debts with each other.

Without circular payments: it's impossible to make the arbitrage from outside the loop.

With circular payments: An external arbitrageur can extract the profit even without orderbooks (since payments are executed from the back to the front, as @JoelKatz explained).
After the circular payment there is more liquidity, since the balances between the branches have been reduced. Is this desirable?

@robcat Hi Roberto, I don't get how an external arbitrageur could use those internal trustlines? If they are completely disconnected it means there are no books with their IOUs (trustlines) and the trust is only between them...no way an external can use a payment involving them.

I don't think circular payments require trustlines to be set up, they depend on (funded parts of) offers and/or direct rippling, if enabled (which could be viewed as implicit offer). They will modify various trust lines as a result, but they operate on order books mostly.

The idea is to be able to submit a path that instead of going via various hops from A over B to C, will end up at A again. This means you already need to own some of A to begin with and at the end of the payment you'd ideally have more of A than you started out with.

As far as I understand JoelKatz's concerns he thinks it would be difficult to allow/evaluate "sub-circles" (so starting at A and ending at A, but also in between reaching B twice). While this might also be a desirable thing (you identify e.g. 4 profitable circles that start and end at XRP in the current ledger and submit a single transaction that consumes all 4 at the same time, taking the increased output of the first one as the input of the second one and so on) this would likely make it harder to evaluate how much money actually is sent/received if you just enter generic amounts (e.g. "spend/receive as much as possible").

@robcat Hi Roberto, I don't get how an external arbitrageur could use those internal trustlines? If they are completely disconnected it means there are no books with their IOUs (trustlines) and the trust is only between them...no way an external can use a payment involving them.

Well, of course it's not possible at the moment (since circular payments are not allowed). In my strange transaction I was using the following observations:

  • A trustline is already created automatically when making self-payments using an orderbook (the arbitrageur could create one beforehand, anyway)
  • The transaction is desirable because it frees up trustlines
  • The invariants on the bank side are respected
  • The payment executes correctly from the back to the front: the balance is first created and then (almost) destroyed, and with no need of trust on the bank side
  • The path forward could execute in the forward direction using a "virtual" trustline, that disappears at the end (very much like a virtual particle)

But in the end it's too complex.

@JoelKatz What about the safety of a very simple loop BTC → EUR → USD → BTC (3 order books, different assets)? I think that the autobridging feature could potentially expand it to BTC → XRP → EUR → XRP → USD → XRP → BTC and create problems.

@robcat I think QualityIn/Out settings on trustlines are probably the most obscure and unusued feature of ripple. I ignore trustlines with those values set as I could never get the figures to reconcile after a Payment used their rippling liquidity.

A trustline is already created automatically when making self-payments using an orderbook.

This is not true for a Payment, but is true for an OfferCreate.

@donovanhide

This is not true for a Payment, but is true for an OfferCreate.

You are right, but any plausible implementation of circular payments I could think of would allow to construct a perfect equivalent of an OfferCreate in the following way:

OfferCreate = {
    TransactionType: OfferCreate,
    Account: sender,
    TakerGets: 5,
    TakerPays: {
      currency: BTC,
      issuer: gateway,
      value: 1
    }
}
SelfPayment = {
  TransactionType : Payment,
  Account : sender,
  Destination : sender,
  SendMax: 5,
  Amount : {
      currency: BTC,
      issuer: gateway,
      value: 1
  },
  Path (can be auto-filled): [
    {account: sender},
    {account: gateway, currency: BTC},
    {account: sender}
  ]
}

I would expect these two equivalent transactions to produce the same effects†, and thus behave in a similar way. In both cases who signs the transaction implicitly agrees to hold the asset he/she tried to buy.

† with the only difference that the effects that OfferCreate are "delayed"

@robcat Your circular payment example isn't circular :-)

The SendMax and Amount would need to have the same currency. For XRP, you'd probably say something like an 999999 Amount and 5 for SendMax, and select the partial payment flag. You don't care how much you receive as long as it is more than the 5 you put in. It's not well documented (at least it wasn't a year or two ago...) but the first and last step in the path are implied and redundant. For the sake of a real example:

[
    {
        "TransactionType": "Payment",
        "Flags": 458752,
        "Account": "rrrrrrrrrrrrrrrrrrrrrhoLvTp",
        "Sequence": 0,
        "Fee": "0",
        "hash": "0000000000000000000000000000000000000000000000000000000000000000",
        "Destination": "rrrrrrrrrrrrrrrrrrrrrhoLvTp",
        "Amount": {
            "value": "26.10927",
            "currency": "GCB",
            "issuer": "rBfVgTnsdh8ckC19RM8aVGNuMZnpwrMP6n"
        },
        "SendMax": "360484",
        "Paths": [
            [
                {
                    "currency": "GCB",
                    "issuer": "rNdwi8ain5ibXNB9A7H3zzKtSxgVzAqqAe",
                    "type": 48,
                    "type_hex": "0000000000000030"
                },
                {
                    "account": "rNdwi8ain5ibXNB9A7H3zzKtSxgVzAqqAe",
                    "type": 1,
                    "type_hex": "0000000000000001"
                },
                {
                    "currency": "BTC",
                    "issuer": "rHSFu2BEV15GY3QmDeFznRXqgXkMhihccC",
                    "type": 48,
                    "type_hex": "0000000000000030"
                },
                {
                    "account": "rHSFu2BEV15GY3QmDeFznRXqgXkMhihccC",
                    "type": 1,
                    "type_hex": "0000000000000001"
                },
                {
                    "currency": "GCB",
                    "issuer": "rBfVgTnsdh8ckC19RM8aVGNuMZnpwrMP6n",
                    "type": 48,
                    "type_hex": "0000000000000030"
                },
                {
                    "account": "rBfVgTnsdh8ckC19RM8aVGNuMZnpwrMP6n",
                    "type": 1,
                    "type_hex": "0000000000000001"
                }
            ]
        ]
    },
    {
        "TransactionType": "OfferCreate",
        "Flags": 655360,
        "Account": "rrrrrrrrrrrrrrrrrrrrrhoLvTp",
        "Sequence": 0,
        "Fee": "0",
        "hash": "0000000000000000000000000000000000000000000000000000000000000000",
        "TakerPays": "402027",
        "TakerGets": {
            "value": "26.373",
            "currency": "GCB",
            "issuer": "rBfVgTnsdh8ckC19RM8aVGNuMZnpwrMP6n"
        }
    }
]

The goal is to remove the need for the OfferCreate step to complete the XRP=>XRP circuit.

The point about trustlines I was making earlier is that it is inconsistent that OfferCreates do not require a pre-existing TrustLine limit set, whereas Payments to the same account do. It makes sense for incoming payments from another account to require this be set, and it also makes sense if you want to provide rippling liquidity. But for the conversion scenario, it is just annoying :-)

@donovanhide I agree on the Trust line for payments, but it is not a big deal, you make a script to set all the lines and you're done forever. The annoying part is that you need two transactions. And that has two drawbacks:
-You need the liquidity of another currency (GCB in your example) or you need to send the second transaction in the next ledger, after you receive the first payment.
-With two transactions it is most likely that one succeed and the other not, that is super annoying because you are left with a currency you didn't want.

I see a very cheap solution to this: remove the error, check the forward-backward, if it fails just fail the transaction, otherwise succeed...so it is up to the designer of the path to find a "good" path without internal loops, if that was the only problem to circular payments.

@donovanhide how long did it take to generate the rogue account? :) how many wallet were created before?

@robcat Your circular payment example isn't circular :-)

The SendMax and Amount would need to have the same currency. For XRP, you'd probably say something like an 999999 Amount and 5 for SendMax, and select the partial payment flag. You don't care how much you receive as long as it is more than the 5 you put in. It's not well documented (at least it wasn't a year or two ago...) but the first and last step in the path are implied and redundant. For the sake of a real example:

[
  {
      "TransactionType": "Payment",
      "Flags": 458752,
      "Account": "rrrrrrrrrrrrrrrrrrrrrhoLvTp",
      "Sequence": 0,
      "Fee": "0",
      "hash": "0000000000000000000000000000000000000000000000000000000000000000",
      "Destination": "rrrrrrrrrrrrrrrrrrrrrhoLvTp",
      "Amount": {
          "value": "26.10927",
          "currency": "GCB",
          "issuer": "rBfVgTnsdh8ckC19RM8aVGNuMZnpwrMP6n"
      },
      "SendMax": "360484",
      "Paths": [
          [
              {
                  "currency": "GCB",
                  "issuer": "rNdwi8ain5ibXNB9A7H3zzKtSxgVzAqqAe",
                  "type": 48,
                  "type_hex": "0000000000000030"
              },
              {
                  "account": "rNdwi8ain5ibXNB9A7H3zzKtSxgVzAqqAe",
                  "type": 1,
                  "type_hex": "0000000000000001"
              },
              {
                  "currency": "BTC",
                  "issuer": "rHSFu2BEV15GY3QmDeFznRXqgXkMhihccC",
                  "type": 48,
                  "type_hex": "0000000000000030"
              },
              {
                  "account": "rHSFu2BEV15GY3QmDeFznRXqgXkMhihccC",
                  "type": 1,
                  "type_hex": "0000000000000001"
              },
              {
                  "currency": "GCB",
                  "issuer": "rBfVgTnsdh8ckC19RM8aVGNuMZnpwrMP6n",
                  "type": 48,
                  "type_hex": "0000000000000030"
              },
              {
                  "account": "rBfVgTnsdh8ckC19RM8aVGNuMZnpwrMP6n",
                  "type": 1,
                  "type_hex": "0000000000000001"
              }
          ]
      ]
  },
  {
      "TransactionType": "OfferCreate",
      "Flags": 655360,
      "Account": "rrrrrrrrrrrrrrrrrrrrrhoLvTp",
      "Sequence": 0,
      "Fee": "0",
      "hash": "0000000000000000000000000000000000000000000000000000000000000000",
      "TakerPays": "402027",
      "TakerGets": {
          "value": "26.373",
          "currency": "GCB",
          "issuer": "rBfVgTnsdh8ckC19RM8aVGNuMZnpwrMP6n"
      }
  }
]

The goal is to remove the need for the OfferCreate step to complete the XRP=>XRP circuit.

The point about trustlines I was making earlier is that it is inconsistent that OfferCreates do not require a pre-existing TrustLine limit set, whereas Payments to the same account do. It makes sense for incoming payments from another account to require this be set, and it also makes sense if you want to provide rippling liquidity. But for the conversion scenario, it is just annoying :-)

Hi @donovanhide,

May I ask which gateway rHSFu2BEV15GY3QmDeFznRXqgXkMhihccC belongs to? I got a trouble with my ripple account, and I forget which BTC gateway I am using. I only know I have this address. I am new to ripple, thanks.

It seems this user (r954fqRdUW2w72uBRMuB4igexVRmu95jjn) was able to find a smart trick or workaround to do circular payments by adding an external trust lines (For example this transaction: 645CD2ADB4B45376F39BDB9E895D431AF9C364BD984E9F4E223A5332950B8269).
I don't know why this kind of transactions isn't forbidden by rippled since the path passes twice from the same currency basically making it a circular payment.

This is an old issue - closing the issue. There will be significant performance implications to the ledger if implemented. We can open a new issue if needed.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mDuo13 picture mDuo13  Â·  6Comments

anhcao142 picture anhcao142  Â·  5Comments

mDuo13 picture mDuo13  Â·  4Comments

XieXiaomei-ptr picture XieXiaomei-ptr  Â·  3Comments

mDuo13 picture mDuo13  Â·  5Comments