CP102: Deposit additional CFG into HydraDX Omnipool

@ImdioR Q: Why 750k CFG vs 710k CFG?
A: The original calculations targeted a total Centrifuge Treasury deposit of $1M. Same for each of the chains I’m working with. But market conditions have changed with several coins lower in value. No one had given feedback about the exact number of tokens so I increased it slightly while staying under 5% of the treasury. If you would prefer 710k CFG, please let me know. Existing 500k + 750k new deposit would result in total treasury deposit of 1250k CFG or $831.6k.

@WilliamFreude I pulled the CFG token location from Hydradx chain state assetRegistry→assetLocations(13).
X2: [ { Parachain: 2,031 } { GeneralKey: { length: 2 data: 0x0001000000000000000000000000000000000000000000000000000000000000 } } ]

@WilliamFreude, the known Hydra restrictions are:

  1. You can only deposit or withdraw up to 5% of the existing pool liquidity for a token. On the Liquidity page you can try to add CFG liquidity and the UI will inform you that the current max for a single deposit is 47475 CFG.

  1. Therefore we need to use the scheduler pallet to break the large deposit into chunks. The Hydra chain includes the scheduler pallet, but it can only be used by governance. Luckily the Centrifuge chain includes the scheduler pallet, so Centrifuge governance call can schedule repeated XCM calls to deposit each chunk of CFG liquidity.

  2. One negative side effect of breaking up the liquidity deposit into multiple chunks is that you receive a separate NFT representing each LP position. In the Wallet → Liquidity Positions area of the HydraDX UI, it shows each LP NFT as a separate ‘card’. I have requested the Hydra team to also have a stacked view that shows the cumulative stats for all LP positions of a single token. That would make it easier for all of us to check the performance of the treasury deposit at:

https://app.hydradx.io/wallet/assets?account=7LCt6dFsJVukxnxpix9KcTkwu2kWQnXARsy6BuBHEL54NcS6

Therefore the overall strategy for this Centrifuge governance call is:

  1. Transfer the 710k/750k CFG to Centrifuge sibling acct on HydraDX (plus 20 CFG for fees buffer)
  2. schedule once every 1+ block repeating 16 times to dispatch a call from Treasury to deposit 46,875 CFG into the Omnipool.
    0x3e02083e0300016d6f646c70792f747273727900000000000000000000000000000000000000007c00000000904cbb5f69aad29e00000000000003010200c91f01007369626cef070000000000000000000000000000000000000000000000000000003f040100000001010000001000000000790003010100c91f0314000400010200bd1f060200010000000000000000000000000000000000000000000000000000000000000013000064a7b3b6e00d1300010200bd1f060200010000000000000000000000000000000000000000000000000000000000000013000064a7b3b6e00d0006010700e40b540202000400583b020d00000000008c239bb14d19ed09000000000000140d0100000101007369626cef070000000000000000000000000000000000000000000000000000

I documented my setup recently here: https://github.com/AcalaNetwork/chopsticks/issues/751

  1. clone and build Chopsticks on Ubuntu.

  2. Set LOG_LEVEL="debug". (there’s a more verbose trace level but it’s too busy.)

  3. Launch Chopsticks with
    npx @acala-network/chopsticks@latest xcm -r polkadot -p interlay -p centrifuge -p hydradx
    or
    npx @acala-network/chopsticks@latest xcm -r polkadot --parachain=configs/interlay.yml --parachain=configs/centrifuge.yml --parachain=configs/hydradx.yml –parachain=configs/phala.yml

  4. Open a browser window and connect Polkadot.js/apps UI to the Chopsticks chains like https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A8001#/explorer

  5. Pull up HydraDX in p.js/apps and check chain state before running a test.

    • Centrifuge sibling acct balances: tokens→account(Sibling 2031)
    • Centrifuge sibling acct LP positions: uniques→account(Sibling 2031, 1337)
  6. Open the Centrifuge JS Console and insert this snippet to run extrinsics from root (like governance). They execute immediately. Ignore the specific extrinsic inserted below; you’ll overwrite it.

  const number = (await api.rpc.chain.getHeader()).number.toNumber()
  await api.rpc('dev_setStorage', {
   scheduler: {
     agenda: [
       [
         [number + 1], [
           {
             call: {
               Inline: '0x4d047369626cd6070000000000000000000000000000000000000000000000000000090000001f00001062fde3f00184770400'
             },
             origin: {
               system: 'Root'
             }
           }
         ]
       ]
     ]
   }
  })
  await api.rpc('dev_newBlock', { count: 1})
  1. From Centrifuge JS console execute the following to send 750,020 CFG from Centrifuge Treasury to Hydra:

0x3e0300016d6f646c70792f747273727900000000000000000000000000000000000000007c00000000904cbb5f69aad29e00000000000003010200c91f01007369626cef07000000000000000000000000000000000000000000000000000000

  1. From Centrifuge JS console execute the following to remotely deposit 46,875 CFG into Omnipool using CFG for XCM fees. This fails with Corrupted State in Chopsticks console. XCM message is never sent. (this uses SovereignAccount)
    0x790003010100c91f0314000400010200bd1f060200010000000000000000000000000000000000000000000000000000000000000013000064a7b3b6e00d1300010200bd1f060200010000000000000000000000000000000000000000000000000000000000000013000064a7b3b6e00d000601130000e8890423c78a02000400583b020d00000000008c239bb14d19ed09000000000000140d0100010200c91f01007369626cef070000000000000000000000000000000000000000000000000000

Regarding how much CFG to Withdraw, here are several values I tried and their resulting errors:
10^18 - Chopsticks Corrupted state and nothing in CFG block explorer
10^14 - Chopsticks Corrupted state and nothing in CFG block explorer
10^12 - HydraDX block shows both XCM errors WeightLimitReached and OverweightEnqueued
10^11 - HydraDX block shows both XCM errors WeightLimitReached and OverweightEnqueued
10^10 - HydraDX - Asset Trap “TooExpensive”

Yesterday I studied Polkadot refs 231 and 457 which are the only examples demonstrating paying for transactions on Hydra. I observe that they do Transact from SovereignAccount and they Transact with a time that is 10x the amount of the WithdrawAsset and BuyExecution. So withdraw 10^9 and transact for max of 10^10. Those two refs generally pay XCM fees in DOT, although 231 pays for transfers to AssetHub using USDT.

Based on that I was able to send an XCM call from Centrifuge to Hydra which succeeded in using DOT for the XCM fee. :tada: The DOT must be held by the Centrifuge sibling acct. If DOT is only held in the Centrifuge treasury acct on Centrifuge chain, or held in the Child acct on Polkadot relay it will not work.

  1. execute 0x4d047369626cef07000000000000000000000000000000000000000000000000000005000000070010a5d4e800 from Hydra JS console to put 100 DOT in the Centrifuge sibling acct

  2. From Centrifuge JS console successfully execute the following to remotely deposit 46,875 CFG into Omnipool using DOT for XCM fees.
    0x790003010100c91f031400040001000002286bee130001000002286bee0006010700e40b540202000400583b020d00000000008c239bb14d19ed09000000000000140d0100010200c91f01007369626cef070000000000000000000000000000000000000000000000000000

So it is currently my belief that you can only pay XCM fees with DOT and sufficient assets from AssetHub (and that’s why francisco.aguirre and acatangiu were very focused on their solution of using pallet-asset-conversion to convert to DOT to pay fees here). But I’d love to hear that you’ve found a way to craft an XCM call that pays in CFG.

Tips:

  • Chopsticks config for Hydra chain needs runtime-log-level: 5 for better errors. I’ll submit a PR.
  • always craft your extrinsics in a non-Chopsticks chain browser. If you restart chopsticks to restart your scenario it resets all of the P.js/apps pages.
  • In addition to Chopsticks errors on the command line, I found the P.js/apps Block Explorer to be very useful to see error messages on Centrifuge and Hydra chains.
  • In your address book for HydraDX, store the addresses for sibling accounts for all of the parachains you’re interested in. Makes it easier to fill in extrinsics fields by just typing 2031.
  • the best XCM docs I’ve seen are Moonbeam’s: https://docs.moonbeam.network/builders/interoperability/xcm/send-execute-xcm/

Another error scenario I’ve seen:

  • Use X2(Parachain 2034, AccountId32=Sibling2031) as XCM call destination. That results in Corrupted State. Instead just send to X1(Parachain 2034).

Does this mean that whenever Centrifuge decides to remove the liquidity and transfer CFG tokens back the max amount will be 5% per transaction? :thinking:

This is outside of the Centrifuge Governance forum and Centrifuge proposal.

Looks like even HydraDX token dropped in value. Isn`t it?

In the prev HydraDX proposal 500,000 CFG were transferred in a single transaction:

I wonder why this time we need to split the payment?

This restriction should be written in the proposal (IMHO), because they are restricting in a significant way Centrifuge actions in the future and all token holders should be aware of this before they will vote for this proposal.

@spazcoin @jrafaelangarita @jgreen Can anyone from the HydraDX Team join our Governance call tomorrow?

New information about a maximum of 5% withdrawal could generate some questions that our community might ask you.
This tech detail wasn`t included in the initial description of this proposal.

this is a circuit breaker or a security measure that was added to prevent price manipulations, by adding and withdrawing large quantities of LPs

The team is already looking at how to allow large entities and users to remove large amounts without making x numbers of txs, so that is simply a security measure

link: Gradual Add/Remove Liquidity · Issue #802 · galacticcouncil/HydraDX-node · GitHub

so in the meanwhile acn coordinate with the hydra governance to do it on 1 tx via referedum, can withdraw via xcm calls with scheduler, or wait for the new solution and will be able to do it on on one referedum with this new extrinsic or solution

and on the first one not was needed, cause the LP of the centrifuge was the one that initiated the Trading on Omnipool, not have price before, so was done via governance, like all other assets

Thank you for answering this.
I wonder why this is not a priority task for HD team.
HydraDX always emphasized the key difference of your product which is the full control (which means full withdrawal at any time) of tokens from the liquidity provider.
And right now we discover that full control of tokens means only 5% per time…Interesting.

Can you please indicate:

  • How often does this situation occur from the Protocol who provided the liquidity (not the private one)?
  • which protocol manipulated price difference by adding and withdrawing large quantities of LPs?

It was a possible vector attack , which has been mitigated by adding this security measure, I don’t see anything strange

As I said, it is still possible to do it, if necessary, through a referendum with a scheduler like the one spazcoin has done above, so it is still under the control of governance :sweat_smile::person_shrugging:, the link that i sent to you is to made something to will help to be done on 1 extrinsic, or easy way.

= RFC: Listing CFG in HydraDX Omnipool & seeding initial liquidity

  • only 5% at once or via multiple tx…
  • Only via HydraDX governance

The initial proposal of @jgreen was pretty clear about the ownership and full control:

  • Trustlessly and without losing control of the funds
  • The CFG position can be withdrawn at any time

If this is possible to do right now only via HydraDX governance (CFG protocol has right now 0 HDX tokens and is in the full minority) this means that all CFG tokens are already in control of HydraDX governance right now and the HydraDX token holder could even vote NAY and reject the proposal of withdrawing CFG Liquidity.

This is definitely not FULL control of the funds and definitely not at ANY Time if

  1. CFG DAO should rely on HydraDX governance or should submit 25-30 transactions in order to withdraw the funds.
  2. You can not do this whenever you want but should do this gradually…

This solution with HydraDX Governance and 5% sounds like depositing funds into the bank account and with the restriction to withdraw only 250$ a day max (of course with a well-written slogan: Your funds are safu and they are in your full control)

  • Is there any timeline for when the solution of returning back the lost control of the funds will be provided by the HydraDX Team to the Protocol and main LPs providers?

the tokens are on centrifuge sibling account and you can do this with 1 referedum on centrifuge parachain that will do the needed txs, even if it is 1 or 10, that is the power of actions powered by governance, you put this on the governance call, not like you will do 20 referedum, so you still have the full control of the tokens, idk what you mean😅

when the solution is added i will ping you

Hey @ImdioR :wave:,

I wanted to address some of your concerns. These points still hold true:

  • Trustlessly and without losing control of the funds
  • The CFG position can be withdrawn at any time

You do NOT need HydraDX governance to perform the withdraw. CFG governance can withdraw the whole position with one referendum. Currently this can be achieved using pallet scheduler which repeats withdrawal of an amount <5% of CFG TVL during multiple (consecutive) blocks, so it will just take a few minutes instead of having it instantly. Wondering why you perceive it as a problem rather then a good safeguard? Having this limit is an essential security measure against price manipulation attacks, protecting your treasury funds.

Proposed alternative solution to add new liquidity in one go via HydraDX governance is absolutely optional, you don’t need HDX governance to add the liquidity either. It is just used for convenience, as root can bypass the 5% limit.

That being said you’re right in the sense that it is not as simple as one would wish. We’ll make sure to prioritize the task to have a possibility to do this with one extrinsic that will automate the scheduling as well.

With regards, jgreen

1 Like

Thank you for answering @jgreen
Yesterday during the call @spazcoin explained this.
This was misconception due to this statement:

That I initially was thinking that required involving Hydra Governance and Hydra Token holders

Just my curiosity.
Does this even add some sort of security in case if anyone can withdraw 1,000,000 CFG in less that 30 blocks+some delay between executions of multiple transactions?

Yes, it makes the attack economically non-feasible.

1 Like

Here’s a working version of the second call with transaction fees in CFG:

0x790003010100c91f0314000400010200bd1f06020001000000000000000000000000000000000000000000000000000000000000001300008a5d784563011300010200bd1f06020001000000000000000000000000000000000000000000000000000000000000001300008a5d784563010006010700f2052a0142130200583b020d00000000008c239bb14d19ed09000000000000140d0100010100bd1f

Differences to your call:

  1. The refTime weight for the transact call was set to 5_000_000_000 instead of your configured 1_000_000_000_000_000_000, which is 200000000x higher.

Screenshot 2024-05-17 at 16.46.55

The DMP (handler for downward messages from Polkadot) configuration of HydraDX is limited to 10_000_000_000 which is 2x of my above refTime. You can check this value at api.query.dmpQueue.configuration().

  1. The origin kind of the transact had to be changed from Native to SovereignAccount because the call should be from the Sibling(2031) account.
    Screenshot 2024-05-17 at 16.45.20

  2. The amount of liquidity was lowered from ~46.7k CFG to 42.0k because anything closer to the original value failed at the point of testing. I suppose pool liquidity dropped a bit. The new encoded call hash of the transact call is 0x3b020d00000000008c239bb14d19ed09000000000000.

  1. Last but not least, the recipient of the over paid transaction fees was configured incorrectly. It is now set to the Sibling(2031) account via (1, X1(Parachain(2031)) MultiLocation

Proof of success
Since I went the lazy path of wrapping everything into SUDO, there might be still be a typo even though I tripple checked everything. Unfortunately, your scheduler idea didn’t work out for me.

2 Likes

BREAKTHROUGH!!

  1. @WilliamFreude Thanks for debugging and all of the tips! I really appreciate your notes about the refTime and will use SovereignAccount. I’ll still directly refer to the Centrifuge account number on HydraDX since the syntax is more explicit.

  2. I also received EXTREMELY useful help from ermalkaleci with Chopsticks, who discovered that the root of my issue testing extrinsics is that the inline call is limited to 128 characters.
    comment: silent fail or corrupted state from XCM calls? · Issue #751 · AcalaNetwork/chopsticks · GitHub
    code: https://github.com/paritytech/polkadot-sdk/blob/d05786ffb5523c334f10d16870c2e73674661a52/substrate/frame/support/src/traits/preimages.rs#L29

I promise I’ll return to learning the Typescript that Ermal suggested and William I will test and learn your sudo method also. However, the immediate easiest thing is just to submit each very long extrinsic as a preimage (within Chopsticks) and then execute it from the JS console with:

const number = (await api.rpc.chain.getHeader()).number.toNumber()
await api.rpc('dev_setStorage', {
 scheduler: {
   agenda: [
     [
       [number + 1], [
         {
           call: {
             Lookup: {
               hash: '0x515b70543e1530cf269523853d5e3abb4c7220caa283ccd2c947fef7ef846ec9',
               len: 304
             }
           },
           origin: {
             system: 'Root'
           }
         }
       ]
     ]
   ]
 }
})
await api.rpc('dev_newBlock', { count: 22})

Using that method I could successfully run jgreen’s XCM call and William’s XCM call. I was also able to successfully run … drumroll … a version using Centrifuge’s scheduler to perform all of the HydraDX Omnipool liquidity deposits within a single CFG governance call !!

Therefore the overall strategy for this Centrifuge governance call is:

  1. Transfer the 750k CFG to Centrifuge sibling acct on HydraDX (plus 20 CFG for fees buffer)
  2. schedule once every 1 block repeating 20 times to deposit 37,500 CFG into the Omnipool.
    0x3e02083e0300016d6f646c70792f747273727900000000000000000000000000000000000000007c00000000904cbb5f69aad29e00000000000003010200c91f01007369626cef070000000000000000000000000000000000000000000000000000003f040100000001010000001400000000790003010100c91f0314000400010200bd1f060200010000000000000000000000000000000000000000000000000000000000000013000064a7b3b6e00d1300010200bd1f060200010000000000000000000000000000000000000000000000000000000000000013000064a7b3b6e00d0006010700f2052a0102000400583b020d0000000000701c7cf40ae1f007000000000000140d0100000101007369626cef070000000000000000000000000000000000000000000000000000

This has submitted for official review (and eventual governance voting) as preimage 0x515b70543e1530cf269523853d5e3abb4c7220caa283ccd2c947fef7ef846ec9

1 Like

Proof of success:

  1. Centrifuge Sibling acct on Hydra now contains 20 additional LP NFTs:

  2. Centrifuge Sibling acct on Hydra now holds about 19.557 CFG, demonstrating that only 0.443 CFG were used to pay the XCM fees and the rest were successfully returned to the sibling acct on Hydra.

1 Like

I hereby confirm your statements, .i.e.

  1. The preimage 0x515b70543e1530cf269523853d5e3abb4c7220caa283ccd2c947fef7ef846ec9 successfully schedules the transfer of 750,020 CFG from the Centrifuge Treasury to the Centrifuge Sibling account on HydraDX and provides 20x37,500 CFG as Liqudity to the OmniPool
  2. The total fee is indeed only ~0.443 CFG.
  3. Testing via your Chopsticks scheduler approach works.

@spazcoin Could you share the call hash which you used to register the preimage? I want to fully make sure there is no side logic attached to it because Polkadot Js Apps only decoded until the third Transact.

2 Likes