Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BAL Hookathon - NFT ESCROW HOOK #92

Open
wants to merge 92 commits into
base: main
Choose a base branch
from
Open

Conversation

tonynacumoto
Copy link

@tonynacumoto tonynacumoto commented Oct 8, 2024

🏦 NFT ESCROW HOOK

NftCheckHook.sol is a Balancer v3 hook to allow a liquidity pool to be backed by an NFT. This is accomplished by staking it into this escrow hook which mints an ERC20 to represent it fractionally. The hook also enables the depositor to settle the pool at the current market rate - in essence this is buying paying for all outstanding NFT-based ERC20 tokens (aka linked or asset tokens) by depositing the required amount of the counterpart (stable) token into the escrow hook contract which then releases the NFT. Then the holders of the linked tokens can redeem their linked tokens for stable tokens using the hook.

Think of it is as a pool that requires the current linked token value to be honored in the equivalent counterpart (stable) token and held in escrow to be redeemed by linked token holders.

The NftCheckHook was designed with RWA NFTs in mind, yet is applicable on any NFT.

spyros-zikos wallet address: 0x24708B03D32265D3E050aC65D1Ea1c0033b4a019

Tony Nacumoto wallet address: 0x1DEA6076bC003a957B1E4774A93a8D9aB0CBC1C1


📜 Table of Contents


🏡 Use Case

This use case makes the assumption that we have a weighted pool combined with the NftCheckHook. However, we haven't tested this and our code and demo use the constant sum pool. The difference is that on the constant sum pool there is no price adjustment as required by this use case.

A user has created an NFT that represents a real world asset, such as an AirBnB. In order to sell parts of their AirBnB and see what the dynamic market value of their asset is they create a liquidity pool with 20% of the equity paired with 20% of a stable coin pegging the price to $100,000. They stake their RWA NFT in order to assure the token holders that infact there is an NFT that represents the asset, and assure the potential buyers that it is not being used in any other financial instrument.

Swapping occurs as guests in the AirBnB are given the opportunity to invest. The price increases 20% as supply is decreased in the pool. In time the owner discovers a new financing defi product that also requires the NFT and desires to withdraw. Since it would be nearly impossible to contact the existing token holders and purchase them back, he is given the ability to deposit the stable coin needed in order to cover the existing outstanding tokens. If there are 10 tokens out of 100 that are not in the pool or in the depositor's wallet and with the asset price of $120,000 he must deposit $12,000 into the liquidity pool in order to withdraw his NFT. Token holders may now redeem their 10 tokens, each one being worth $1,200.


🧔 User Flow

  • Mint RWA NFT
  • Create NftCheckHook which mint the NFT owner some linked "RWAT" ERC20 tokens
  • Create Pool with RWAT tokens & MST stable tokens
  • Stake NFT into hook
  • Initialize Pool
  • Swapping Occurs
  • Settlement is initiated by the NFT owner, transfering stable tokens to the hook and the hook returns the NFT to the owner
  • Initial liquidity is withdrawn by the owner
  • Oustanding linked tokens are redeemed for MST from the hook contract

📹 Demo

https://www.youtube.com/watch?v=0zS-bFA9sNE&feature=youtu.be


NOTES:

  1. The MockStable balance of the hook is greater than 10 because of the 1.1 multiplier minus the fees. We use the 1.1 multiplier as an extra way to reward the user for getting the nft-based erc20 tokens.

🪝 Utilized Hooks

onRegister
Ensure that pool supports donations, update global variables, emit hook registration event.
onBeforeInitialize
We require that the NFT is deposited and that one of the tokens in the pool is the cooresponding linked erc20 token. Initial liquidity values are recorded to be referenced later to ensure the initial depositor does not withdraw more than their initial deposit.
onAfterRemoveLiquidity
Checking to make sure that the depositor has not removed more tokens than they originally deposited. This is our anti-rug pull check, locking in the liquidity until after the NFT has been withdrawn.
onBeforeSwap
Check if pool has been settled in order to halt trading if so.

📙 Technical Notes

There are a few functions that we needed to add to the hook in order to make this work such as settle, redeem, getSettlementAmount, recordInitialLiquidity & setNft. We could also add some additional functions to that allow for some advanced functionality, such as detaching the linked token upon settle and minting a new fresh one for the NFT upon withdrawl but figure that will be it's own process. We also thought of combining the settle with liquidity withdrawl, but it was a more complex hook use case so kept it in two seperate operations. Ideally we will extend this hook to work with the weighted pool. This will allow for smaller amount of capital to be used to peg an asset to a particular price but will need to take the ratio into account for the settlement amount.


🙏 Acknowledgments

Many thanks from elamore and Tony Nacu to daniel | Beethoven X, matthu.eth, Tritium and burns for answering the big and small questions that we had. Without their help this project would not be possible.


🚀 Future

We'd like to continue developing this in order to fit into an entire ecosystem where a user can utilize the value of their asset, as determined via the pool, for use in a loan product. This would enable the LTV to be responsive to market price and could enable other novel hook use cases. This doesn't only apply to RWA NFTs but any NFT that wants to provide liquidity.

At the onset of the hackathon we started on another governance reward hook in addition to our nft escrow, and developing a pattern for how to compose hooks would be ideal so that we can overlay a "governance reward" hook along with the "nft escrow" hook without having to have all the logic in the same hook.

Along those same lines we will be planning to use this hook on all 3 pool types -- constant sum, product & weighted -- and as such will most likely need to refactor parts in order to accomodate.

Also adding cross-chain multi-assest compatibility so that there is only one liquidity pool for an asset, but is accessible via any chain and any asset ie "zap in" would be usefull going forward.

tonynacumoto and others added 26 commits September 24, 2024 12:08
…e until the onRegister function can be fully uncommented
…ity portion but hit roadblock so is commented out
Copy link

vercel bot commented Oct 8, 2024

@tonynacumoto is attempting to deploy a commit to the Matt Pereira's projects Team on Vercel.

A member of the Team first needs to authorize it.

@tonynacumoto tonynacumoto changed the title BAL Hookathon - RWA NFT LPv3 HOOK BAL Hookathon - RWA NFT LPv3 Oct 8, 2024
@tonynacumoto tonynacumoto changed the title BAL Hookathon - RWA NFT LPv3 BAL Hookathon - NFT ESCROW HOOK Oct 21, 2024
revert LinkedTokenNotInPool(linkedToken);
}
// Record the initial liquidity amounts
recordInitialLiquidity(tokenConfigs[0].token.balanceOf(poolAddress), tokenConfigs[1].token.balanceOf(poolAddress));
Copy link

@danielmkm danielmkm Oct 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The balances will always be zero, the pool never holds the tokens. The vault does.

) public override onlyVault returns (bool, uint256[] memory hookAdjustedAmountsOutRaw) {

// Ensure the first depositor has the same amount of both tokens after removal (no rug pulling allowed)
if (msg.sender == owner()) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

msg.sender would be the vault here, not sure how this works in your demo video.

IERC20[] memory poolTokens = _vault.getPoolTokens(pool);

uint256 currentToken1Amount = poolTokens[0].balanceOf(msg.sender);
uint256 currentToken2Amount = poolTokens[1].balanceOf(msg.sender);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the balance of the vault, this is not correct.

if (currentToken2Amount < initialToken2Amount) {
revert InsufficientLiquidityToRemove(msg.sender, currentToken2Amount, initialToken2Amount);
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not prevent a rug. In the instance that msg.sender was the user (which it's not, it's the vault). Consider the scenario:

  1. NFT owner transfers NFT and then adds initial liquidity
  2. NFT owner transfers BPT to a new wallet, that is now the owner.
  3. New wallet calls remove liquidity, and bypasses the checks above.

uint256[] memory accruedFees = new uint256[](tokens.length);
hookAdjustedAmountsOutRaw = amountsOutRaw;

if (exitFeePercentage > 0) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is there an exit fee? In what instance would there be more than one LP in this pool? If there's only one LP, who are you donating fees too in the lines below?

spyros-zikos and others added 11 commits November 7, 2024 14:18
…_swap helper function, wait for tests + coms overhead 1h10'
… test different values of the fee by changing the INITIAL_SETTLEMENT_FEE variable 1h20'
…tling the pool and one test where owner removes liquidity after settling the pool 1h40'
…deemWithFee. the hook had to call a function of the vault that it inherits from the VaultExtension contract. also deleted the contents of onBeforeAddLiquidity hook because it was wrong and caused errors. Also +10 minutes for the bpt thingy. 1h50
…th constant product pool. Used a real pool and not a mock one like constant sum tests do. 1h30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants