diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index 6c7a77887..de29524ec 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -21,7 +21,7 @@ jobs: # Report code coverage to discord - name: Generate coverage - run: forge coverage --report lcov --no-match-test testLoad + run: forge coverage --report lcov --no-match-test "testLoad|invariant" - name: Setup LCOV uses: hrishikesh-kadam/setup-lcov@v1 - name: Filter lcov diff --git a/.github/workflows/forge-tests.yml b/.github/workflows/forge-tests.yml deleted file mode 100644 index b77869bef..000000000 --- a/.github/workflows/forge-tests.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: Forge Tests - -on: [push] - -env: - ## Loads environment from secrets - ETH_RPC_URL: ${{secrets.ETH_RPC_URL}} - -jobs: - tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - submodules: recursive - - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - with: - version: nightly - - - name: Create ids - shell: bash - run: | - echo "##[set-output name=dir;]$(echo ${GITHUB_REF#refs/heads/}-gas-reports)" - echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})" - id: gas - - - name: Cache gas snasphot - id: cache-gas - uses: actions/cache@v3 - with: - path: gas-reports-* - key: ${{ steps.gas.outputs.dir }}-${{ github.run_id }} - restore-keys: | - ${{ steps.gas.outputs.dir }}- - - - name: Run tests and compare gas - shell: bash - run: | - prevRuns=$(find . -name "gas-reports-*" -printf '.' | wc -m) - if [ $prevRuns = "0" ]; then echo "No previous run"; else dir=$(ls -td -- gas-reports-* | head -n 1) && forge snapshot --no-match-test testLoad --diff $dir/.gas-snapshot;fi 2>&1 | tee gas-diff-output.txt - mkdir -p gas-reports-${{ github.run_id }} - forge snapshot --no-match-test testLoad --gas-report --snap gas-reports-${{ github.run_id }}/.gas-snapshot - echo "##[set-output name=gas-diff;]$(echo $(cat gas-diff-output.txt|tail -1|sed -e 's/\x1b\[[0-9;]*m//g'))" - id: forge-tests - - - name: Send to Discord - uses: appleboy/discord-action@master - with: - webhook_id: ${{ secrets.DISCORD_ID }} - webhook_token: ${{ secrets.DISCORD_TOKEN }} - message: https://github.com/ajna-finance/contracts/commit/${{ github.sha }} ```${{ steps.forge-tests.outputs.gas-diff }} compared with last run in branch ${{ steps.gas.outputs.branch }}``` - - timeout-minutes: 10 \ No newline at end of file diff --git a/.github/workflows/size-check.yml b/.github/workflows/size-check.yml index de68bb5a8..10f623fcd 100644 --- a/.github/workflows/size-check.yml +++ b/.github/workflows/size-check.yml @@ -9,7 +9,8 @@ jobs: submodules: recursive - name: Cache compiler installations - uses: actions/cache@v2 + id: cache-compiler + uses: actions/cache@v3 with: path: | ~/.solcx @@ -32,13 +33,13 @@ jobs: - name: Set pip cache directory path id: pip-cache-dir-path run: | - echo "::set-output name=dir::$(pip cache dir)" + echo "PIP_CACHE_DIR=$(pip cache dir)" >> $GITHUB_ENV - name: Restore pip cache - uses: actions/cache@v2 + uses: actions/cache@v3 id: pip-cache with: path: | - ${{ steps.pip-cache-dir-path.outputs.dir }} + ${PIP_CACHE_DIR} key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements-dev.txt') }} restore-keys: | ${{ runner.os }}-pip-${{ hashFiles('**/requirements-dev.txt') }} @@ -47,11 +48,11 @@ jobs: run: pip install -r requirements-dev.txt - name: Run check-size script + id: check-size run: | mkdir -p size-reports-${{ github.run_id }} ./check-size.sh | tee size-reports-${{github.run_id}}/size-report - echo "##[set-output name=size-report;]$(echo $(cat size-reports-${{github.run_id}}/size-report|tail -1))" - id: check-size + echo "SIZE_REPORT=$(echo $(cat size-reports-${{github.run_id}}/size-report|tail -1))" >> $GITHUB_OUTPUT - name: Send size report to Discord uses: appleboy/discord-action@master @@ -59,5 +60,5 @@ jobs: webhook_id: ${{ secrets.DISCORD_ID }} webhook_token: ${{ secrets.DISCORD_TOKEN }} username: "Contract Size Reporter" - message: Largest contract size for `${{ github.ref }}` ```${{ steps.check-size.outputs.size-report }}``` - timeout-minutes: 2 \ No newline at end of file + message: Largest contract size for `${{ github.ref }}` ```${{ steps.check-size.outputs.SIZE_REPORT }}``` + timeout-minutes: 3 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5e9c34d5d..1357170a3 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,5 @@ reports/ coverage/ *.info report/ -keystore/ \ No newline at end of file +keystore/ +broadcast/ \ No newline at end of file diff --git a/Makefile b/Makefile index 80d2ddfff..5a0c59780 100644 --- a/Makefile +++ b/Makefile @@ -14,13 +14,20 @@ install :; git submodule update --init --recursive build :; forge clean && forge build # Tests -test :; forge test --no-match-test testLoad # --ffi # enable if you need the `ffi` cheat code on HEVM -test-with-gas-report :; FOUNDRY_PROFILE=optimized forge test --no-match-test testLoad --gas-report # --ffi # enable if you need the `ffi` cheat code on HEVM +test :; forge test --no-match-test "testLoad|invariant" # --ffi # enable if you need the `ffi` cheat code on HEVM +test-with-gas-report :; FOUNDRY_PROFILE=optimized forge test --no-match-test "testLoad|invariant" --gas-report # --ffi # enable if you need the `ffi` cheat code on HEVM test-load :; FOUNDRY_PROFILE=optimized forge test --match-test testLoad --gas-report -coverage :; forge coverage --no-match-test testLoad +test-invariant :; forge t --mt invariant +coverage :; forge coverage --no-match-test "testLoad|invariant" # Generate Gas Snapshots snapshot :; forge clean && forge snapshot analyze: slither src/. ; slither src/libraries/external/. + + +# Deployment +deploy-contracts: + forge script ./deploy.sol \ + --rpc-url ${ETH_RPC_URL} --sender ${DEPLOY_ADDRESS} --keystore ${DEPLOY_KEY} --broadcast -vvv diff --git a/README.md b/README.md index 834b02766..22eeddcb6 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,21 @@ The Ajna protocol is a non-custodial, peer-to-peer, permissionless lending, borrowing and trading system that requires no governance or external price feeds to function. The protocol consists of pools: pairings of quote tokens provided by lenders and collateral tokens provided by borrowers. Ajna is capable of accepting fungible tokens as quote tokens and both fungible and non-fungible tokens as collateral tokens. -## Limitations -- The following types of tokens are incompatible with Ajna, and no countermeasures exist to explicitly prevent creating a pool with such tokens: - - Fungible tokens whose balance rebases. - - NFTs which charge a fee on transfer. - - Fungible tokens with more than 18 decimals or 0 decimals. +## Accepted tokens: +- Fungible tokens (following the [ERC20 token standard](https://eips.ethereum.org/EIPS/eip-20)). +- Non-fungible tokens (following the [ERC721 token standard](https://eips.ethereum.org/EIPS/eip-721)) - Special considerations have been made to support specific NFTs with nonstandard ERC721 implementations, including _CryptoPunks_ and _CryptoKitties_. This support is limited to Ethereum mainnet. + +### Token limitations +- The following types of tokens are incompatible with Ajna, and no countermeasures exist to explicitly prevent creating a pool with such tokens, actors should use them at their own risk: + - NFT and fungible tokens which charge a fee on transfer. + - Fungible tokens whose balance rebases. + - Fungible tokens with more than 18 decimals or 0 decimals. - Borrowers cannot draw debt from a pool in the same block as when the pool was created. - With the exception of quantized prices, pool inputs and most accumulators are not explicitly limited. The pool will stop functioning when the bounds of a `uint256` need to be exceeded to process a request. + ## Development ### Requirements - `python` 3.0+ @@ -150,35 +155,38 @@ Modifications to, or notices of actions by Licensor, contemplated above or under ## Deployment -A deployment script has been created to automate deployment of libraries and factory contracts. -To use it, set up an environment with the following: -- **AJNA_TOKEN** - address of the AJNA token on your target chain -- **ETH_RPC_URL** - node pointing to the target chain -- **DEPLOY_KEY** - path to the JSON keystore file for your deployment account +A deployment script has been created to automate deployment of libraries, factory contracts, and manager contracts. + +To use it, ensure the following env variables are in your `.env` file or exported into your environment. +| Environment Variable | Purpose | +|----------------------|---------| +| `AJNA_TOKEN` | address of the AJNA token on your target chain +| `DEPLOY_ADDRESS` | address from which you wish to deploy +| `DEPLOY_KEY` | path to the JSON keystore file for the deployment address +| `ETHERSCAN_API_KEY` | required to verify contracts +| `ETH_RPC_URL` | node on your target deployment network -Ensure your deployment account is funded with some ETH for gas. -The deployment script takes no arguments, and interactively prompts for your keystore password: +Since contract source has not yet been made public, the `--verify` switch has been omitted. To run: + ``` -./deploy.sh +make deploy-contracts ``` Upon completion, contract addresses will be printed to `stdout`: ``` -Deploying to chain with AJNA token address 0xDD576260ed60AaAb798D8ECa9bdBf33D70E077F4 -Enter keystore password: -Deploying libraries... -Deployed Auctions to 0xDD576260ed60AaAb798D8ECa9bdBf33D70E077F4 -Deployed LenderActions to 0x4c08A2ec1f5C067DC53A5fCc36C649501F403b93 -Deployed PoolCommons to 0x8BBCA51044d00dbf16aaB8Fd6cbC5B45503B898b -Deploying factories... -Deployed ERC20PoolFactory to 0xED625fbf62695A13d2cADEdd954b23cc97249988 -Deployed ERC721PoolFactory to 0x775D30918A42160bC7aE56BA1660E32ff50CF6dC -Deploying PoolInfoUtils... -Deployed PoolInfoUtils to 0xd8A51cE16c7665111401C0Ba2ABEcE03B847b4e6 +== Logs == + Deploying to chain with AJNA token address 0x34A1D3fff3958843C43aD80F30b94c510645C... + === Deployment addresses === + ERC20 factory 0x50EEf481cae4250d252Ae577A09bF514f224C... + ERC721 factory 0x62c20Aa1e0272312BC100b4e23B4DC1Ed96dD... + PoolInfoUtils 0xDEb1E9a6Be7Baf84208BB6E10aC9F9bbE1D70... + PositionManager 0xD718d5A27a29FF1cD22403426084bA0d47986... + RewardsManager 0x4f559F30f5eB88D635FDe1548C4267DB8FaB0... + ``` -Record the factory addresses. +Record these addresses. ### Validation @@ -203,4 +211,4 @@ cast send ${DAI_TOKEN} "approve(address,uint256)" ${WBTC_DAI_POOL} 50000ether \ --from ${DEPLOY_ADDRESS} --keystore ${DEPLOY_KEY} cast send ${WBTC_DAI_POOL} "addQuoteToken(uint256,uint256)" 100ether 3232 \ --from ${DEPLOY_ADDRESS} --keystore ${DEPLOY_KEY} -``` +``` \ No newline at end of file diff --git a/check-code-coverage.sh b/check-code-coverage.sh index 72fdb5be2..9bc6ba7ae 100755 --- a/check-code-coverage.sh +++ b/check-code-coverage.sh @@ -1,6 +1,6 @@ #!/bin/bash -forge coverage --report lcov --no-match-test testLoad +forge coverage --report lcov --no-match-test "testLoad|invariant" lcov -r lcov.info "tests/*" -o lcov-filtered.info --rc lcov_branch_coverage=1 diff --git a/deploy.sh b/deploy.sh deleted file mode 100755 index c23e79d98..000000000 --- a/deploy.sh +++ /dev/null @@ -1,63 +0,0 @@ -#!/bin/bash - -# load environment variables from .env file -set -o allexport -source .env -set +o allexport - -echo Deploying to chain with AJNA token address ${AJNA_TOKEN:?} - -read -p "Enter keystore password: " -s password - -regex="Deployed to: ([0-9xa-fA-F]+)" -linkage=() - -echo -echo Deploying libraries... -libraries=( Auctions LenderActions BorrowerActions PoolCommons PositionNFTSVG ) -for contract in "${libraries[@]}" -do - createlib="forge create --rpc-url ${ETH_RPC_URL:?} --keystore ${DEPLOY_KEY:?} --password ${password:?} \ - src/libraries/external/$contract.sol:$contract" - output=$($createlib) - if [[ $output =~ $regex ]] - then - address=${BASH_REMATCH[1]} - printf "Deployed %20s to %s\n" ${contract:0:20} $address - linkage+="--libraries src/libraries/external/$contract.sol:$contract:$address " - else - echo $contract was not deployed: $output - exit 1 - fi -done - -echo Deploying factories... -factories=( ERC20PoolFactory ERC721PoolFactory ) -for contract in "${factories[@]}" -do - createfactory="forge create --rpc-url ${ETH_RPC_URL:?} --keystore ${DEPLOY_KEY:?} --password ${password:?} \ - src/$contract.sol:$contract --constructor-args ${AJNA_TOKEN:?} ${linkage}" - output=$($createfactory) - if [[ $output =~ $regex ]] - then - address=${BASH_REMATCH[1]} - printf "Deployed %20s to %s\n" ${contract:0:20} $address - else - echo $contract was not deployed: $output - exit 2 - fi -done - -echo Deploying PoolInfoUtils... -contract=PoolInfoUtils -create="forge create --rpc-url ${ETH_RPC_URL:?} --keystore ${DEPLOY_KEY:?} --password ${password:?} \ - src/$contract.sol:$contract ${linkage}" -output=$($create) -if [[ $output =~ $regex ]] -then - address=${BASH_REMATCH[1]} - printf "Deployed %20s to %s\n" ${contract:0:20} $address -else - echo $contract was not deployed: $output - exit 1 -fi \ No newline at end of file diff --git a/deploy.sol b/deploy.sol new file mode 100644 index 000000000..3b50b91f6 --- /dev/null +++ b/deploy.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.14; + +import { Script } from "forge-std/Script.sol"; +import "forge-std/console.sol"; + +import { ERC20PoolFactory } from 'src/ERC20PoolFactory.sol'; +import { ERC721PoolFactory } from 'src/ERC721PoolFactory.sol'; +import { PoolInfoUtils } from 'src/PoolInfoUtils.sol'; +import { PositionManager } from 'src/PositionManager.sol'; +import { RewardsManager } from 'src/RewardsManager.sol'; + +contract Deploy is Script { + address ajna; + + function run() public { + ajna = vm.envAddress("AJNA_TOKEN"); + console.log("Deploying to chain with AJNA token address %s", ajna); + + vm.startBroadcast(); + ERC20PoolFactory erc20factory = new ERC20PoolFactory(ajna); + ERC721PoolFactory erc721factory = new ERC721PoolFactory(ajna); + PoolInfoUtils poolInfoUtils = new PoolInfoUtils(); + + PositionManager positionManager = new PositionManager(erc20factory, erc721factory); + RewardsManager rewardsManager = new RewardsManager(ajna, positionManager); + vm.stopBroadcast(); + + console.log("=== Deployment addresses ==="); + console.log("ERC20 factory %s", address(erc20factory)); + console.log("ERC721 factory %s", address(erc721factory)); + console.log("PoolInfoUtils %s", address(poolInfoUtils)); + console.log("PositionManager %s", address(positionManager)); + console.log("RewardsManager %s", address(rewardsManager)); + } +} \ No newline at end of file diff --git a/docs/Functions.md b/docs/Functions.md index ac1d3b0c8..7e12e88ea 100644 --- a/docs/Functions.md +++ b/docs/Functions.md @@ -129,7 +129,7 @@ emit events: - LenderActions.transferLPs(): - - TransferLPTokens + - TransferLPs ### kick external libraries call: @@ -370,7 +370,7 @@ - BorrowerActions.drawDebt() - borrower not sender BorrowerNotSender() - borrower debt less than pool min debt AmountLTMinDebt() - - limit price reached LimitIndexReached() + - limit price reached LimitIndexExceeded() - borrower cannot draw more debt BorrowerUnderCollateralized() emit events: diff --git a/docs/drawio/addCollateral.drawio b/docs/drawio/addCollateral.drawio new file mode 100644 index 000000000..b62d8a395 --- /dev/null +++ b/docs/drawio/addCollateral.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/docs/drawio/addQuoteToken.drawio b/docs/drawio/addQuoteToken.drawio new file mode 100644 index 000000000..0ea7fef45 --- /dev/null +++ b/docs/drawio/addQuoteToken.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/docs/drawio/bucketTake.drawio b/docs/drawio/bucketTake.drawio new file mode 100644 index 000000000..942266631 --- /dev/null +++ b/docs/drawio/bucketTake.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/docs/drawio/components.drawio b/docs/drawio/components.drawio new file mode 100644 index 000000000..ddebbac67 --- /dev/null +++ b/docs/drawio/components.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/docs/drawio/drawDebt.drawio b/docs/drawio/drawDebt.drawio new file mode 100644 index 000000000..2653b64ad --- /dev/null +++ b/docs/drawio/drawDebt.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/docs/drawio/kick.drawio b/docs/drawio/kick.drawio new file mode 100644 index 000000000..c4758a438 --- /dev/null +++ b/docs/drawio/kick.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/docs/drawio/kickWithDeposit.drawio b/docs/drawio/kickWithDeposit.drawio new file mode 100644 index 000000000..6c1076aed --- /dev/null +++ b/docs/drawio/kickWithDeposit.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/docs/drawio/moveQuoteToken.drawio b/docs/drawio/moveQuoteToken.drawio new file mode 100644 index 000000000..5ec0cbef3 --- /dev/null +++ b/docs/drawio/moveQuoteToken.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/docs/drawio/poolContractsArchitecture.drawio b/docs/drawio/poolContractsArchitecture.drawio new file mode 100644 index 000000000..6000897e3 --- /dev/null +++ b/docs/drawio/poolContractsArchitecture.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/docs/drawio/removeCollateral.drawio b/docs/drawio/removeCollateral.drawio new file mode 100644 index 000000000..b1f8199cb --- /dev/null +++ b/docs/drawio/removeCollateral.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/docs/drawio/removeQuoteToken.drawio b/docs/drawio/removeQuoteToken.drawio new file mode 100644 index 000000000..dfe6768ca --- /dev/null +++ b/docs/drawio/removeQuoteToken.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/docs/drawio/repayDebt.drawio b/docs/drawio/repayDebt.drawio new file mode 100644 index 000000000..a9a221166 --- /dev/null +++ b/docs/drawio/repayDebt.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/docs/drawio/settle.drawio b/docs/drawio/settle.drawio new file mode 100644 index 000000000..30d3e9999 --- /dev/null +++ b/docs/drawio/settle.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/docs/drawio/take.drawio b/docs/drawio/take.drawio new file mode 100644 index 000000000..f8a6de366 --- /dev/null +++ b/docs/drawio/take.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/docs/html/addCollateral.html b/docs/html/addCollateral.html new file mode 100644 index 000000000..2e0301cd4 --- /dev/null +++ b/docs/html/addCollateral.html @@ -0,0 +1,11 @@ + + + + +addCollateral + + +
+ + + \ No newline at end of file diff --git a/docs/html/addQuoteToken.html b/docs/html/addQuoteToken.html new file mode 100644 index 000000000..11fd15bda --- /dev/null +++ b/docs/html/addQuoteToken.html @@ -0,0 +1,11 @@ + + + + +addQuoteToken + + +
+ + + \ No newline at end of file diff --git a/docs/html/bucketTake.html b/docs/html/bucketTake.html new file mode 100644 index 000000000..ff4d63be4 --- /dev/null +++ b/docs/html/bucketTake.html @@ -0,0 +1,11 @@ + + + + +bucketTake + + +
+ + + \ No newline at end of file diff --git a/docs/html/components.html b/docs/html/components.html new file mode 100644 index 000000000..08bd8ff7a --- /dev/null +++ b/docs/html/components.html @@ -0,0 +1,11 @@ + + + + +components + + +
+ + + \ No newline at end of file diff --git a/docs/html/drawDebt.html b/docs/html/drawDebt.html new file mode 100644 index 000000000..1e57c7099 --- /dev/null +++ b/docs/html/drawDebt.html @@ -0,0 +1,11 @@ + + + + +drawDebt + + +
+ + + \ No newline at end of file diff --git a/docs/html/kick.html b/docs/html/kick.html new file mode 100644 index 000000000..af55df35c --- /dev/null +++ b/docs/html/kick.html @@ -0,0 +1,11 @@ + + + + +kick + + +
+ + + \ No newline at end of file diff --git a/docs/html/kickWithDeposit.html b/docs/html/kickWithDeposit.html new file mode 100644 index 000000000..45b15fa7b --- /dev/null +++ b/docs/html/kickWithDeposit.html @@ -0,0 +1,11 @@ + + + + +kickWithDeposit + + +
+ + + \ No newline at end of file diff --git a/docs/html/moveQuoteToken.html b/docs/html/moveQuoteToken.html new file mode 100644 index 000000000..4df0727f6 --- /dev/null +++ b/docs/html/moveQuoteToken.html @@ -0,0 +1,11 @@ + + + + +moveQuoteToken + + +
+ + + \ No newline at end of file diff --git a/docs/html/poolContractsArchitecture.html b/docs/html/poolContractsArchitecture.html new file mode 100644 index 000000000..4bd8403d6 --- /dev/null +++ b/docs/html/poolContractsArchitecture.html @@ -0,0 +1,11 @@ + + + + +Untitled Diagram-Page-1 + + +
+ + + \ No newline at end of file diff --git a/docs/html/removeCollateral.html b/docs/html/removeCollateral.html new file mode 100644 index 000000000..b7622c8af --- /dev/null +++ b/docs/html/removeCollateral.html @@ -0,0 +1,11 @@ + + + + +removeCollateral + + +
+ + + \ No newline at end of file diff --git a/docs/html/removeQuoteToken.html b/docs/html/removeQuoteToken.html new file mode 100644 index 000000000..5c04479b9 --- /dev/null +++ b/docs/html/removeQuoteToken.html @@ -0,0 +1,11 @@ + + + + +removeQuoteToken + + +
+ + + \ No newline at end of file diff --git a/docs/html/repayDebt.html b/docs/html/repayDebt.html new file mode 100644 index 000000000..02d7c2d78 --- /dev/null +++ b/docs/html/repayDebt.html @@ -0,0 +1,11 @@ + + + + +repayDebt + + +
+ + + \ No newline at end of file diff --git a/docs/html/settle.html b/docs/html/settle.html new file mode 100644 index 000000000..e8fedd42e --- /dev/null +++ b/docs/html/settle.html @@ -0,0 +1,11 @@ + + + + +addCollateral + + +
+ + + \ No newline at end of file diff --git a/docs/html/take.html b/docs/html/take.html new file mode 100644 index 000000000..308899a7d --- /dev/null +++ b/docs/html/take.html @@ -0,0 +1,11 @@ + + + + +take + + +
+ + + \ No newline at end of file diff --git a/docs/jpeg/ajnaContractsArchitecture.jpeg b/docs/jpeg/ajnaContractsArchitecture.jpeg new file mode 100644 index 000000000..7a4ae3937 Binary files /dev/null and b/docs/jpeg/ajnaContractsArchitecture.jpeg differ diff --git a/docs/jpeg/poolContract.jpeg b/docs/jpeg/poolContract.jpeg new file mode 100644 index 000000000..5612d316d Binary files /dev/null and b/docs/jpeg/poolContract.jpeg differ diff --git a/docs/jpeg/poolContractsArchitecture.jpeg b/docs/jpeg/poolContractsArchitecture.jpeg new file mode 100644 index 000000000..03048a0b3 Binary files /dev/null and b/docs/jpeg/poolContractsArchitecture.jpeg differ diff --git a/foundry.toml b/foundry.toml index 55b2b54cc..c1b3e82d2 100644 --- a/foundry.toml +++ b/foundry.toml @@ -24,3 +24,9 @@ optimizer_runs = 200 [fuzz] runs = 300 + +[invariant] +runs = 10 # The number of calls to make in the invariant tests +depth = 100 # The number of times to run the invariant tests +call_override = false # Override calls +fail_on_revert = false # Fail the test if the contract reverts \ No newline at end of file diff --git a/src/ERC20Pool.sol b/src/ERC20Pool.sol index 4f90e78c8..f139b8ce5 100644 --- a/src/ERC20Pool.sol +++ b/src/ERC20Pool.sol @@ -15,8 +15,7 @@ import { IERC20Taker } from './interfaces/pool/erc20/IERC20Taker.sol'; import { IPoolLenderActions, - IPoolLiquidationActions, - IERC20Token + IPoolLiquidationActions } from './interfaces/pool/IPool.sol'; import { IERC3156FlashBorrower, @@ -39,7 +38,10 @@ import { _roundToScale, _roundUpToScale } from './libraries/helpers/PoolHelper.sol'; -import { _revertIfAuctionClearable } from './libraries/helpers/RevertsHelper.sol'; +import { + _revertIfAuctionClearable, + _revertOnExpiry +} from './libraries/helpers/RevertsHelper.sol'; import { Loans } from './libraries/internal/Loans.sol'; import { Deposits } from './libraries/internal/Deposits.sol'; @@ -165,7 +167,7 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { if (amountToBorrow_ != 0) { // update pool balances state - poolBalances.t0Debt += result.t0DebtChange; + poolBalances.t0Debt = result.t0PoolDebt; // move borrowed amount from pool to sender _transferQuoteToken(msg.sender, amountToBorrow_); @@ -184,7 +186,9 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { function repayDebt( address borrowerAddress_, uint256 maxQuoteTokenAmountToRepay_, - uint256 collateralAmountToPull_ + uint256 collateralAmountToPull_, + address collateralReceiver_, + uint256 limitIndex_ ) external nonReentrant { PoolState memory poolState = _accruePoolInterest(); @@ -200,7 +204,8 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { poolState, borrowerAddress_, maxQuoteTokenAmountToRepay_, - collateralAmountToPull_ + collateralAmountToPull_, + limitIndex_ ); emit RepayDebt(borrowerAddress_, result.quoteTokenToRepay, collateralAmountToPull_, result.newLup); @@ -212,7 +217,7 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { if (result.quoteTokenToRepay != 0) { // update pool balances state - poolBalances.t0Debt -= result.t0RepaidDebt; + poolBalances.t0Debt = result.t0PoolDebt; if (result.t0DebtInAuctionChange != 0) { poolBalances.t0DebtInAuction -= result.t0DebtInAuctionChange; } @@ -224,52 +229,8 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { // update pool balances state poolBalances.pledgedCollateral = result.poolCollateral; - // move collateral from pool to sender - _transferCollateral(msg.sender, collateralAmountToPull_); - } - } - - /************************************/ - /*** Flashloan External Functions ***/ - /************************************/ - - /// @inheritdoc FlashloanablePool - function flashLoan( - IERC3156FlashBorrower receiver_, - address token_, - uint256 amount_, - bytes calldata data_ - ) external override(IERC3156FlashLender, FlashloanablePool) nonReentrant returns (bool) { - if (token_ == _getArgAddress(QUOTE_ADDRESS)) return _flashLoanQuoteToken(receiver_, token_, amount_, data_); - - if (token_ == _getArgAddress(COLLATERAL_ADDRESS)) { - _transferCollateral(address(receiver_), amount_); - - if (receiver_.onFlashLoan(msg.sender, token_, amount_, 0, data_) != - keccak256("ERC3156FlashBorrower.onFlashLoan")) revert FlashloanCallbackFailed(); - - _transferCollateralFrom(address(receiver_), amount_); - return true; - } - - revert FlashloanUnavailableForToken(); - } - - /// @inheritdoc FlashloanablePool - function flashFee( - address token_, - uint256 - ) external pure override(IERC3156FlashLender, FlashloanablePool) returns (uint256) { - if (token_ == _getArgAddress(QUOTE_ADDRESS) || token_ == _getArgAddress(COLLATERAL_ADDRESS)) return 0; - revert FlashloanUnavailableForToken(); - } - - /// @inheritdoc FlashloanablePool - function maxFlashLoan( - address token_ - ) external view override(IERC3156FlashLender, FlashloanablePool) returns (uint256 maxLoan_) { - if (token_ == _getArgAddress(QUOTE_ADDRESS) || token_ == _getArgAddress(COLLATERAL_ADDRESS)) { - maxLoan_ = IERC20Token(token_).balanceOf(address(this)); + // move collateral from pool to address specified as collateral receiver + _transferCollateral(collateralReceiver_, collateralAmountToPull_); } } @@ -286,8 +247,10 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { */ function addCollateral( uint256 amountToAdd_, - uint256 index_ + uint256 index_, + uint256 expiry_ ) external override nonReentrant returns (uint256 bucketLPs_) { + _revertOnExpiry(expiry_); PoolState memory poolState = _accruePoolInterest(); // revert if the dust amount was not exceeded, but round on the scale amount @@ -359,12 +322,11 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { ) external override nonReentrant { PoolState memory poolState = _accruePoolInterest(); - uint256 assets = Maths.wmul(poolBalances.t0Debt, poolState.inflator) + _getPoolQuoteTokenBalance(); + uint256 assets = Maths.wmul(poolBalances.t0Debt, poolState.inflator) + _getNormalizedPoolQuoteTokenBalance(); uint256 liabilities = Deposits.treeSum(deposits) + auctions.totalBondEscrowed + reserveAuction.unclaimed; ( - , , uint256 collateralSettled, uint256 t0DebtSettled @@ -427,18 +389,11 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { result.quoteTokenAmount = _roundUpToScale(result.quoteTokenAmount, _getArgUint256(QUOTE_SCALE)); // update pool balances state - uint256 t0PoolDebt = poolBalances.t0Debt; uint256 t0DebtInAuction = poolBalances.t0DebtInAuction; - - if (result.t0DebtPenalty != 0) { - t0PoolDebt += result.t0DebtPenalty; - t0DebtInAuction += result.t0DebtPenalty; - } - - t0PoolDebt -= result.t0RepayAmount; + t0DebtInAuction += result.t0DebtPenalty; t0DebtInAuction -= result.t0DebtInAuctionChange; - poolBalances.t0Debt = t0PoolDebt; + poolBalances.t0Debt = result.t0PoolDebt; poolBalances.t0DebtInAuction = t0DebtInAuction; poolBalances.pledgedCollateral -= result.collateralAmount; @@ -457,7 +412,7 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { ); } - _transferQuoteTokenFrom(callee_, result.quoteTokenAmount); + _transferQuoteTokenFrom(msg.sender, result.quoteTokenAmount); } /** @@ -488,18 +443,11 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { ); // update pool balances state - uint256 t0PoolDebt = poolBalances.t0Debt; uint256 t0DebtInAuction = poolBalances.t0DebtInAuction; - - if (result.t0DebtPenalty != 0) { - t0PoolDebt += result.t0DebtPenalty; - t0DebtInAuction += result.t0DebtPenalty; - } - - t0PoolDebt -= result.t0RepayAmount; + t0DebtInAuction += result.t0DebtPenalty; t0DebtInAuction -= result.t0DebtInAuctionChange; - poolBalances.t0Debt = t0PoolDebt; + poolBalances.t0Debt = result.t0PoolDebt; poolBalances.t0DebtInAuction = t0DebtInAuction; poolBalances.pledgedCollateral -= result.collateralAmount; @@ -509,6 +457,20 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { _updateInterestState(poolState, result.newLup); } + /***************************/ + /*** Flashloan Functions ***/ + /***************************/ + + /** + * @inheritdoc FlashloanablePool + * @dev Override default implementation and allows flashloans for both quote and collateral token. + */ + function _isFlashloanSupported( + address token_ + ) internal virtual view override returns (bool) { + return token_ == _getArgAddress(QUOTE_ADDRESS) || token_ == _getArgAddress(COLLATERAL_ADDRESS); + } + /************************/ /*** Helper Functions ***/ /************************/ diff --git a/src/ERC20PoolFactory.sol b/src/ERC20PoolFactory.sol index c6ed65b41..6098ea682 100644 --- a/src/ERC20PoolFactory.sol +++ b/src/ERC20PoolFactory.sol @@ -4,8 +4,8 @@ pragma solidity 0.8.14; import { ClonesWithImmutableArgs } from '@clones/ClonesWithImmutableArgs.sol'; -import { IERC20PoolFactory } from './interfaces/pool/erc20/IERC20PoolFactory.sol'; - +import { IERC20PoolFactory } from './interfaces/pool/erc20/IERC20PoolFactory.sol'; +import { IPoolFactory } from './interfaces/pool/IPoolFactory.sol'; import { IERC20Token, PoolType } from './interfaces/pool/IPool.sol'; import { ERC20Pool } from './ERC20Pool.sol'; @@ -49,7 +49,9 @@ contract ERC20PoolFactory is PoolDeployer, IERC20PoolFactory { */ function deployPool( address collateral_, address quote_, uint256 interestRate_ - ) external canDeploy(ERC20_NON_SUBSET_HASH, collateral_, quote_, interestRate_) returns (address pool_) { + ) external canDeploy(collateral_, quote_, interestRate_) returns (address pool_) { + if (deployedPools[ERC20_NON_SUBSET_HASH][collateral_][quote_] != address(0)) revert IPoolFactory.PoolAlreadyExists(); + uint256 quoteTokenScale = 10 ** (18 - IERC20Token(quote_).decimals()); uint256 collateralScale = 10 ** (18 - IERC20Token(collateral_).decimals()); diff --git a/src/ERC721Pool.sol b/src/ERC721Pool.sol index b1bf36b9e..386055912 100644 --- a/src/ERC721Pool.sol +++ b/src/ERC721Pool.sol @@ -24,15 +24,13 @@ import { IERC721PoolLenderActions } from './interfaces/pool/erc721/IERC721Pool.sol'; import { IERC721Taker } from './interfaces/pool/erc721/IERC721Taker.sol'; -import { - ICryptoPunks, - ICryptoKitties, - NFTTypes -} from './interfaces/pool/erc721/IERC721NonStandard.sol'; import { FlashloanablePool } from './base/FlashloanablePool.sol'; -import { _revertIfAuctionClearable } from './libraries/helpers/RevertsHelper.sol'; +import { + _revertIfAuctionClearable, + _revertOnExpiry +} from './libraries/helpers/RevertsHelper.sol'; import { Maths } from './libraries/internal/Maths.sol'; import { Deposits } from './libraries/internal/Deposits.sol'; @@ -64,7 +62,6 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { // immutable args offset uint256 internal constant SUBSET = 93; - uint256 internal constant NFT_TYPE = 125; /***********************/ /*** State Variables ***/ @@ -163,7 +160,7 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { if (result.t0DebtInAuctionChange != 0) { poolBalances.t0DebtInAuction -= result.t0DebtInAuctionChange; } - poolBalances.pledgedCollateral += Maths.wad(tokenIdsToPledge_.length); + poolBalances.pledgedCollateral = result.poolCollateral; // move collateral from sender to pool _transferFromSenderToPool(borrowerTokenIds[borrowerAddress_], tokenIdsToPledge_); @@ -174,7 +171,7 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { // move borrowed amount from pool to sender if (amountToBorrow_ != 0) { // update pool balances state - poolBalances.t0Debt += result.t0DebtChange; + poolBalances.t0Debt = result.t0PoolDebt; // move borrowed amount from pool to sender _transferQuoteToken(msg.sender, amountToBorrow_); @@ -187,14 +184,16 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { * - decrement poolBalances.t0Debt accumulator * - decrement poolBalances.t0DebtInAuction accumulator * - decrement poolBalances.pledgedCollateral accumulator - * - update borrowerTokenIds arrays + * - update borrowerTokenIds and bucketTokenIds arrays * @dev emit events: * - RepayDebt */ function repayDebt( address borrowerAddress_, uint256 maxQuoteTokenAmountToRepay_, - uint256 noOfNFTsToPull_ + uint256 noOfNFTsToPull_, + address collateralReceiver_, + uint256 limitIndex_ ) external nonReentrant { PoolState memory poolState = _accruePoolInterest(); @@ -206,7 +205,8 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { poolState, borrowerAddress_, maxQuoteTokenAmountToRepay_, - Maths.wad(noOfNFTsToPull_) + Maths.wad(noOfNFTsToPull_), + limitIndex_ ); emit RepayDebt(borrowerAddress_, result.quoteTokenToRepay, noOfNFTsToPull_, result.newLup); @@ -218,9 +218,12 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { poolState.collateral = result.poolCollateral; _updateInterestState(poolState, result.newLup); + // update pool balances state + poolBalances.pledgedCollateral = result.poolCollateral; + if (result.quoteTokenToRepay != 0) { // update pool balances state - poolBalances.t0Debt -= result.t0RepaidDebt; + poolBalances.t0Debt = result.t0PoolDebt; if (result.t0DebtInAuctionChange != 0) { poolBalances.t0DebtInAuction -= result.t0DebtInAuctionChange; } @@ -229,11 +232,8 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { _transferQuoteTokenFrom(msg.sender, result.quoteTokenToRepay); } if (noOfNFTsToPull_ != 0) { - // update pool balances state - poolBalances.pledgedCollateral = result.poolCollateral; - - // move collateral from pool to sender - _transferFromPoolToAddress(msg.sender, borrowerTokenIds[msg.sender], noOfNFTsToPull_); + // move collateral from pool to address specified as collateral receiver + _transferFromPoolToAddress(collateralReceiver_, borrowerTokenIds[msg.sender], noOfNFTsToPull_); } } @@ -244,14 +244,16 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { /** * @inheritdoc IERC721PoolLenderActions * @dev write state: - * - update borrowerTokenIds arrays + * - update bucketTokenIds arrays * @dev emit events: * - AddCollateralNFT */ function addCollateral( uint256[] calldata tokenIdsToAdd_, - uint256 index_ + uint256 index_, + uint256 expiry_ ) external override nonReentrant returns (uint256 bucketLPs_) { + _revertOnExpiry(expiry_); PoolState memory poolState = _accruePoolInterest(); bucketLPs_ = LenderActions.addCollateral( @@ -282,6 +284,8 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { uint256 noOfNFTsToRemove_, uint256 toIndex_ ) external override nonReentrant returns (uint256 collateralMerged_, uint256 bucketLPs_) { + _revertIfAuctionClearable(auctions, loans); + PoolState memory poolState = _accruePoolInterest(); uint256 collateralAmount = Maths.wad(noOfNFTsToRemove_); @@ -356,7 +360,8 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { ) external nonReentrant override { PoolState memory poolState = _accruePoolInterest(); - uint256 assets = Maths.wmul(poolBalances.t0Debt, poolState.inflator) + _getPoolQuoteTokenBalance(); + uint256 assets = Maths.wmul(poolBalances.t0Debt, poolState.inflator) + _getNormalizedPoolQuoteTokenBalance(); + uint256 liabilities = Deposits.treeSum(deposits) + auctions.totalBondEscrowed + reserveAuction.unclaimed; SettleParams memory params = SettleParams( @@ -370,7 +375,6 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { ); ( uint256 collateralRemaining, - uint256 t0DebtRemaining, uint256 collateralSettled, uint256 t0DebtSettled ) = Auctions.settlePoolDebt( @@ -381,8 +385,7 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { params ); - // slither-disable-next-line incorrect-equality - if (t0DebtRemaining == 0) _rebalanceTokens(params.borrower, collateralRemaining); + if (collateralSettled > 0) _rebalanceTokens(params.borrower, collateralRemaining); // update pool balances state poolBalances.t0Debt -= t0DebtSettled; @@ -422,24 +425,20 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { ); // update pool balances state - uint256 t0PoolDebt = poolBalances.t0Debt; uint256 t0DebtInAuction = poolBalances.t0DebtInAuction; - - if (result.t0DebtPenalty != 0) { - t0PoolDebt += result.t0DebtPenalty; - t0DebtInAuction += result.t0DebtPenalty; - } - - t0PoolDebt -= result.t0RepayAmount; + t0DebtInAuction += result.t0DebtPenalty; t0DebtInAuction -= result.t0DebtInAuctionChange; - poolBalances.t0Debt = t0PoolDebt; - poolBalances.t0DebtInAuction = t0DebtInAuction; - poolBalances.pledgedCollateral -= result.collateralAmount; + poolBalances.t0Debt = result.t0PoolDebt; + poolBalances.t0DebtInAuction = t0DebtInAuction; + + // the total collateral taken from borrower pledged collateral (collateral taken plus collateral compensated if auction settled) + uint256 collateralSettled = result.collateralAmount + result.compensatedCollateral; + poolBalances.pledgedCollateral -= collateralSettled; // update pool interest rate state poolState.debt = result.poolDebt; - poolState.collateral -= result.collateralAmount; + poolState.collateral -= collateralSettled; _updateInterestState(poolState, result.newLup); // transfer rounded collateral from pool to taker @@ -449,10 +448,12 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { result.collateralAmount / 1e18 ); + uint256 totalQuoteTokenAmount = result.quoteTokenAmount + result.excessQuoteToken; + if (data_.length != 0) { IERC721Taker(callee_).atomicSwapCallback( tokensTaken, - result.quoteTokenAmount / _getArgUint256(QUOTE_SCALE), + totalQuoteTokenAmount / _getArgUint256(QUOTE_SCALE), data_ ); } @@ -460,7 +461,7 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { if (result.settledAuction) _rebalanceTokens(borrowerAddress_, result.remainingCollateral); // transfer from taker to pool the amount of quote tokens needed to cover collateral auctioned (including excess for rounded collateral) - _transferQuoteTokenFrom(callee_, result.quoteTokenAmount + result.excessQuoteToken); + _transferQuoteTokenFrom(msg.sender, totalQuoteTokenAmount); // transfer from pool to borrower the excess of quote tokens after rounding collateral auctioned if (result.excessQuoteToken != 0) _transferQuoteToken(borrowerAddress_, result.excessQuoteToken); @@ -494,24 +495,20 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { ); // update pool balances state - uint256 t0PoolDebt = poolBalances.t0Debt; uint256 t0DebtInAuction = poolBalances.t0DebtInAuction; - - if (result.t0DebtPenalty != 0) { - t0PoolDebt += result.t0DebtPenalty; - t0DebtInAuction += result.t0DebtPenalty; - } - - t0PoolDebt -= result.t0RepayAmount; + t0DebtInAuction += result.t0DebtPenalty; t0DebtInAuction -= result.t0DebtInAuctionChange; - poolBalances.t0Debt = t0PoolDebt; - poolBalances.t0DebtInAuction = t0DebtInAuction; - poolBalances.pledgedCollateral -= result.collateralAmount; + poolBalances.t0Debt = result.t0PoolDebt; + poolBalances.t0DebtInAuction = t0DebtInAuction; + + // the total collateral taken from borrower pledged collateral (collateral taken plus collateral compensated if auction settled) + uint256 collateralSettled = result.collateralAmount + result.compensatedCollateral; + poolBalances.pledgedCollateral -= collateralSettled; // update pool interest rate state poolState.debt = result.poolDebt; - poolState.collateral -= result.collateralAmount; + poolState.collateral -= collateralSettled; _updateInterestState(poolState, result.newLup); if (result.settledAuction) _rebalanceTokens(borrowerAddress_, result.remainingCollateral); @@ -560,22 +557,13 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { uint256[] calldata tokenIds_ ) internal { bool subset = _getArgUint256(SUBSET) != 0; - uint8 nftType = _getArgUint8(NFT_TYPE); for (uint256 i = 0; i < tokenIds_.length;) { uint256 tokenId = tokenIds_[i]; if (subset && !tokenIdsAllowed[tokenId]) revert OnlySubset(); poolTokens_.push(tokenId); - if (nftType == uint8(NFTTypes.STANDARD_ERC721)){ - _transferNFT(msg.sender, address(this), tokenId); - } - else if (nftType == uint8(NFTTypes.CRYPTOKITTIES)) { - ICryptoKitties(_getArgAddress(COLLATERAL_ADDRESS)).transferFrom(msg.sender ,address(this), tokenId); - } - else{ - ICryptoPunks(_getArgAddress(COLLATERAL_ADDRESS)).buyPunk(tokenId); - } + _transferNFT(msg.sender, address(this), tokenId); unchecked { ++i; } } @@ -598,21 +586,11 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { uint256 noOfNFTsInPool = poolTokens_.length; - uint8 nftType = _getArgUint8(NFT_TYPE); - for (uint256 i = 0; i < amountToRemove_;) { uint256 tokenId = poolTokens_[--noOfNFTsInPool]; // start with transferring the last token added in bucket poolTokens_.pop(); - if (nftType == uint8(NFTTypes.STANDARD_ERC721)){ - _transferNFT(address(this), toAddress_, tokenId); - } - else if (nftType == uint8(NFTTypes.CRYPTOKITTIES)) { - ICryptoKitties(_getArgAddress(COLLATERAL_ADDRESS)).transfer(toAddress_, tokenId); - } - else { - ICryptoPunks(_getArgAddress(COLLATERAL_ADDRESS)).transferPunk(toAddress_, tokenId); - } + _transferNFT(address(this), toAddress_, tokenId); tokensTransferred[i] = tokenId; @@ -624,13 +602,14 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { /** * @dev Helper function to transfer an NFT from owner to target address (reused in code to reduce contract deployment bytecode size). + * @dev Since transferFrom is used instead of safeTransferFrom, calling smart contracts must be careful to check that they support any received NFTs. * @param from_ NFT owner address. * @param to_ New NFT owner address. * @param tokenId_ NFT token id to be transferred. */ function _transferNFT(address from_, address to_, uint256 tokenId_) internal { // slither-disable-next-line calls-loop - IERC721Token(_getArgAddress(COLLATERAL_ADDRESS)).safeTransferFrom(from_, to_, tokenId_); + IERC721Token(_getArgAddress(COLLATERAL_ADDRESS)).transferFrom(from_, to_, tokenId_); } /************************/ diff --git a/src/ERC721PoolFactory.sol b/src/ERC721PoolFactory.sol index 98301a5ce..ef1da0440 100644 --- a/src/ERC721PoolFactory.sol +++ b/src/ERC721PoolFactory.sol @@ -5,9 +5,8 @@ pragma solidity 0.8.14; import { ClonesWithImmutableArgs } from '@clones/ClonesWithImmutableArgs.sol'; import { IERC165 } from '@openzeppelin/contracts/utils/introspection/IERC165.sol'; -import { IERC721PoolFactory } from './interfaces/pool/erc721/IERC721PoolFactory.sol'; -import { NFTTypes } from './interfaces/pool/erc721/IERC721NonStandard.sol'; - +import { IERC721PoolFactory } from './interfaces/pool/erc721/IERC721PoolFactory.sol'; +import { IPoolFactory } from './interfaces/pool/IPoolFactory.sol'; import { IERC20Token, PoolType } from './interfaces/pool/IPool.sol'; import { ERC721Pool } from './ERC721Pool.sol'; @@ -53,29 +52,16 @@ contract ERC721PoolFactory is PoolDeployer, IERC721PoolFactory { */ function deployPool( address collateral_, address quote_, uint256[] memory tokenIds_, uint256 interestRate_ - ) external canDeploy(getNFTSubsetHash(tokenIds_), collateral_, quote_, interestRate_) returns (address pool_) { - uint256 quoteTokenScale = 10**(18 - IERC20Token(quote_).decimals()); + ) external canDeploy(collateral_, quote_, interestRate_) returns (address pool_) { + bytes32 subsetHash = getNFTSubsetHash(tokenIds_); + if (deployedPools[subsetHash][collateral_][quote_] != address(0)) revert IPoolFactory.PoolAlreadyExists(); - NFTTypes nftType; - // CryptoPunks NFTs - if (collateral_ == 0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB ) { - nftType = NFTTypes.CRYPTOPUNKS; - } - // CryptoKitties and CryptoFighters NFTs - else if (collateral_ == 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d || collateral_ == 0x87d598064c736dd0C712D329aFCFAA0Ccc1921A1) { - nftType = NFTTypes.CRYPTOKITTIES; - } - // All other NFTs that support the EIP721 standard - else { - // Here 0x80ac58cd is the ERC721 interface Id - // Neither a standard NFT nor a non-standard supported NFT(punk, kitty or fighter) - try IERC165(collateral_).supportsInterface(0x80ac58cd) returns (bool supportsERC721Interface) { - if (!supportsERC721Interface) revert NFTNotSupported(); - } catch { - revert NFTNotSupported(); - } + uint256 quoteTokenScale = 10**(18 - IERC20Token(quote_).decimals()); - nftType = NFTTypes.STANDARD_ERC721; + try IERC165(collateral_).supportsInterface(0x80ac58cd) returns (bool supportsERC721Interface) { + if (!supportsERC721Interface) revert NFTNotSupported(); + } catch { + revert NFTNotSupported(); } bytes memory data = abi.encodePacked( @@ -84,8 +70,7 @@ contract ERC721PoolFactory is PoolDeployer, IERC721PoolFactory { collateral_, quote_, quoteTokenScale, - tokenIds_.length, - nftType + tokenIds_.length ); ERC721Pool pool = ERC721Pool(address(implementation).clone(data)); @@ -93,7 +78,7 @@ contract ERC721PoolFactory is PoolDeployer, IERC721PoolFactory { pool_ = address(pool); // Track the newly deployed pool - deployedPools[getNFTSubsetHash(tokenIds_)][collateral_][quote_] = pool_; + deployedPools[subsetHash][collateral_][quote_] = pool_; deployedPoolsList.push(pool_); emit PoolCreated(pool_); @@ -105,8 +90,36 @@ contract ERC721PoolFactory is PoolDeployer, IERC721PoolFactory { /*** Pool Creation Functions ***/ /*******************************/ + /** + * @notice Get the hash of the subset of NFTs that will be used to create the pool + * @dev If no tokenIds are provided, the default ERC721_NON_SUBSET_HASH is returned + * @param tokenIds_ The array of token ids that will be used to create the pool + * @return bytes32 The hash of the subset of NFTs that will be used to create the pool + */ function getNFTSubsetHash(uint256[] memory tokenIds_) public pure returns (bytes32) { if (tokenIds_.length == 0) return ERC721_NON_SUBSET_HASH; - else return keccak256(abi.encodePacked(tokenIds_)); + else { + // check the array of token ids is sorted in ascending order + // revert if not sorted + _checkTokenIdSortOrder(tokenIds_); + + // hash the sorted array of tokenIds + return keccak256(abi.encode(tokenIds_)); + } } + + /** + * @notice Check that the array of token ids is sorted in ascending order, else revert. + * @dev The counters are modified in unchecked blocks due to being bounded by array length + * @param tokenIds_ The array of token ids to check for sorting + */ + function _checkTokenIdSortOrder(uint256[] memory tokenIds_) internal pure { + for (uint256 i = 0; i < tokenIds_.length - 1; ) { + if (tokenIds_[i] >= tokenIds_[i + 1]) revert TokenIdSubsetInvalid(); + unchecked { + ++i; + } + } + } + } diff --git a/src/PoolInfoUtils.sol b/src/PoolInfoUtils.sol index 807bf57e9..f395952d6 100644 --- a/src/PoolInfoUtils.sol +++ b/src/PoolInfoUtils.sol @@ -58,13 +58,13 @@ contract PoolInfoUtils { /** * @notice Get a bucket struct for a given index. - * @param index_ The index of the bucket to retrieve. - * @return price_ Bucket price (WAD) - * @return quoteTokens_ Amount of quote token in bucket, deposit + interest (WAD) - * @return collateral_ Unencumbered collateral in bucket (WAD). - * @return bucketLPs_ Outstanding LP balance in bucket (WAD) - * @return scale_ Lender interest multiplier (WAD). - * @return exchangeRate_ The exchange rate of the bucket, in RAY units. + * @param index_ The index of the bucket to retrieve. + * @return price_ Bucket price (WAD) + * @return quoteTokens_ Amount of quote token in bucket, deposit + interest (WAD) + * @return collateral_ Unencumbered collateral in bucket (WAD). + * @return bucketLPs_ Outstanding LP balance in bucket (WAD) + * @return scale_ Lender interest multiplier (WAD). + * @return exchangeRate_ The exchange rate of the bucket, in WAD units. */ function bucketInfo(address ajnaPool_, uint256 index_) external @@ -83,12 +83,7 @@ contract PoolInfoUtils { price_ = _priceAt(index_); (bucketLPs_, collateral_, , quoteTokens_, scale_) = pool.bucketInfo(index_); - if (bucketLPs_ == 0) { - exchangeRate_ = Maths.RAY; - } else { - uint256 bucketSize = quoteTokens_ * 1e18 + price_ * collateral_; // 10^36 + // 10^36 - exchangeRate_ = bucketSize * 1e18 / bucketLPs_; // 10^27 - } + exchangeRate_ = Buckets.getExchangeRate(collateral_, bucketLPs_, quoteTokens_, price_); } /** @@ -187,7 +182,7 @@ contract PoolInfoUtils { (,uint256 poolDebt,) = pool.debtInfo(); uint256 poolSize = pool.depositSize(); - uint256 quoteTokenBalance = IERC20Token(pool.quoteTokenAddress()).balanceOf(ajnaPool_); + uint256 quoteTokenBalance = IERC20Token(pool.quoteTokenAddress()).balanceOf(ajnaPool_) * pool.quoteTokenScale(); (uint256 bondEscrowed, uint256 unclaimedReserve, uint256 auctionKickTime) = pool.reservesInfo(); @@ -352,13 +347,13 @@ contract PoolInfoUtils { /** * @notice Calculate the amount of quote tokens in bucket for a given amount of LP Tokens. - * @param lpTokens_ The number of lpTokens to calculate amounts for. + * @param lps_ The number of LPs to calculate amounts for. * @param index_ The price bucket index for which the value should be calculated. * @return quoteAmount_ The exact amount of quote tokens that can be exchanged for the given LP Tokens, WAD units. */ function lpsToQuoteTokens( address ajnaPool_, - uint256 lpTokens_, + uint256 lps_, uint256 index_ ) external view returns (uint256 quoteAmount_) { IPool pool = IPool(ajnaPool_); @@ -367,7 +362,7 @@ contract PoolInfoUtils { bucketLPs_, bucketCollateral, bucketDeposit, - lpTokens_, + lps_, bucketDeposit, _priceAt(index_) ); @@ -375,13 +370,13 @@ contract PoolInfoUtils { /** * @notice Calculate the amount of collateral tokens in bucket for a given amount of LP Tokens. - * @param lpTokens_ The number of lpTokens to calculate amounts for. + * @param lps_ The number of LPs to calculate amounts for. * @param index_ The price bucket index for which the value should be calculated. * @return collateralAmount_ The exact amount of collateral tokens that can be exchanged for the given LP Tokens, WAD units. */ function lpsToCollateral( address ajnaPool_, - uint256 lpTokens_, + uint256 lps_, uint256 index_ ) external view returns (uint256 collateralAmount_) { IPool pool = IPool(ajnaPool_); @@ -390,7 +385,7 @@ contract PoolInfoUtils { bucketCollateral, bucketLPs_, bucketDeposit, - lpTokens_, + lps_, _priceAt(index_) ); } diff --git a/src/PositionManager.sol b/src/PositionManager.sol index f23de3393..d1caa295b 100644 --- a/src/PositionManager.sol +++ b/src/PositionManager.sol @@ -165,7 +165,7 @@ contract PositionManager is ERC721, PermitERC721, IPositionManager, Multicall, R emit MemorializePosition(owner, params_.tokenId); - // update pool lp token accounting and transfer ownership of lp tokens to PositionManager contract + // update pool lps accounting and transfer ownership of lps to PositionManager contract pool.transferLPs(owner, address(this), params_.indexes); } @@ -180,7 +180,7 @@ contract PositionManager is ERC721, PermitERC721, IPositionManager, Multicall, R */ function mint( MintParams calldata params_ - ) external override returns (uint256 tokenId_) { + ) external override nonReentrant returns (uint256 tokenId_) { tokenId_ = _nextId++; // revert if the address is not a valid Ajna pool @@ -252,7 +252,8 @@ contract PositionManager is ERC721, PermitERC721, IPositionManager, Multicall, R ) = IPool(params_.pool).moveQuoteToken( maxQuote, params_.fromIndex, - params_.toIndex + params_.toIndex, + params_.expiry ); // update position LPs state @@ -307,7 +308,7 @@ contract PositionManager is ERC721, PermitERC721, IPositionManager, Multicall, R emit RedeemPosition(owner, params_.tokenId); - // update pool lp token accounting and transfer ownership of lp tokens from PositionManager contract + // update pool lps accounting and transfer ownership of lps from PositionManager contract pool.transferLPs(address(this), owner, params_.indexes); } @@ -350,7 +351,7 @@ contract PositionManager is ERC721, PermitERC721, IPositionManager, Multicall, R /**********************/ /// @inheritdoc IPositionManagerDerivedState - function getLPTokens( + function getLPs( uint256 tokenId_, uint256 index_ ) external override view returns (uint256) { diff --git a/src/RewardsManager.sol b/src/RewardsManager.sol index 0e0ab2d38..bd406c5a6 100644 --- a/src/RewardsManager.sol +++ b/src/RewardsManager.sol @@ -107,11 +107,13 @@ contract RewardsManager is IRewardsManager { uint256 tokenId_, uint256 epochToClaim_ ) external override { - if (msg.sender != stakes[tokenId_].owner) revert NotOwnerOfDeposit(); + StakeInfo storage stakeInfo = stakes[tokenId_]; + + if (msg.sender != stakeInfo.owner) revert NotOwnerOfDeposit(); if (isEpochClaimed[tokenId_][epochToClaim_]) revert AlreadyClaimed(); - _claimRewards(tokenId_, epochToClaim_); + _claimRewards(stakeInfo, tokenId_, epochToClaim_, true, stakeInfo.ajnaPool); } /** @@ -149,8 +151,8 @@ contract RewardsManager is IRewardsManager { BucketState storage bucketState = stakeInfo.snapshot[bucketId]; - // record the number of lp tokens in bucket at the time of staking - bucketState.lpsAtStakeTime = positionManager.getLPTokens( + // record the number of lps in bucket at the time of staking + bucketState.lpsAtStakeTime = positionManager.getLPs( tokenId_, bucketId ); @@ -173,7 +175,7 @@ contract RewardsManager is IRewardsManager { ); // transfer rewards to sender - IERC20(ajnaToken).safeTransfer(msg.sender, updateReward); + _transferAjnaRewards(updateReward); } /** @@ -187,13 +189,30 @@ contract RewardsManager is IRewardsManager { function unstake( uint256 tokenId_ ) external override { - if (msg.sender != stakes[tokenId_].owner) revert NotOwnerOfDeposit(); + StakeInfo storage stakeInfo = stakes[tokenId_]; - address ajnaPool = stakes[tokenId_].ajnaPool; + if (msg.sender != stakeInfo.owner) revert NotOwnerOfDeposit(); + + address ajnaPool = stakeInfo.ajnaPool; // claim rewards, if any - _claimRewards(tokenId_, IPool(ajnaPool).currentBurnEpoch()); + _claimRewards( + stakeInfo, + tokenId_, + IPool(ajnaPool).currentBurnEpoch(), + false, + ajnaPool + ); + + // remove bucket snapshots recorded at the time of staking + uint256[] memory positionIndexes = positionManager.getPositionIndexes(tokenId_); + for (uint256 i = 0; i < positionIndexes.length; ) { + delete stakeInfo.snapshot[positionIndexes[i]]; // reset BucketState struct for current position + unchecked { ++i; } + } + + // remove recorded stake info delete stakes[tokenId_]; emit Unstake(msg.sender, ajnaPool, tokenId_); @@ -214,7 +233,7 @@ contract RewardsManager is IRewardsManager { updateReward = _updateBucketExchangeRates(pool_, indexes_); // transfer rewards to sender - IERC20(ajnaToken).safeTransfer(msg.sender, updateReward); + _transferAjnaRewards(updateReward); } /*******************************/ @@ -255,7 +274,19 @@ contract RewardsManager is IRewardsManager { return ( stakes[tokenId_].owner, stakes[tokenId_].ajnaPool, - stakes[tokenId_].lastInteractionBurnEpoch); + stakes[tokenId_].lastInteractionBurnEpoch + ); + } + + /// @inheritdoc IRewardsManagerState + function getBucketStateStakeInfo( + uint256 tokenId_, + uint256 bucketId_ + ) external view override returns (uint256, uint256) { + return ( + stakes[tokenId_].snapshot[bucketId_].lpsAtStakeTime, + stakes[tokenId_].snapshot[bucketId_].rateAtStakeTime + ); } /**************************/ @@ -291,15 +322,13 @@ contract RewardsManager is IRewardsManager { positionIndexes ); - uint256 nextEpoch = epoch + 1; - - // update epoch token claim trackers - rewardsClaimed[nextEpoch] += nextEpochRewards; - isEpochClaimed[tokenId_][nextEpoch] = true; - rewards_ += nextEpochRewards; unchecked { ++epoch; } + + // update epoch token claim trackers + rewardsClaimed[epoch] += nextEpochRewards; + isEpochClaimed[tokenId_][epoch] = true; } } @@ -391,7 +420,7 @@ contract RewardsManager is IRewardsManager { // calculate the equivalent amount of quote tokens given the stakes lp balance, // and the exchange rate at the next and current burn events - interestEarned_ = Maths.rayToWad(Maths.rmul(nextExchangeRate - exchangeRate_, bucketLPs)); + interestEarned_ = Maths.wmul(nextExchangeRate - exchangeRate_, bucketLPs); } } @@ -423,8 +452,9 @@ contract RewardsManager is IRewardsManager { // calculate rewards earned newRewards_ = Maths.wmul( REWARD_FACTOR, - Maths.wmul( - Maths.wdiv(interestEarned_, totalInterestEarnedInPeriod), totalBurnedInPeriod + Maths.wdiv( + Maths.wmul(interestEarned_, totalBurnedInPeriod), + totalInterestEarnedInPeriod ) ); @@ -440,47 +470,49 @@ contract RewardsManager is IRewardsManager { /** * @notice Claim rewards that have been accumulated by a staked NFT. - * @param tokenId_ ID of the staked LP NFT. - * @param epochToClaim_ The burn epoch to claim rewards for (rewards calculation starts from the last claimed epoch) + * @param stakeInfo_ Details of stake to claim rewards for. + * @param tokenId_ ID of the staked LP NFT. + * @param epochToClaim_ The burn epoch to claim rewards for (rewards calculation starts from the last claimed epoch) + * @param validateEpoch_ True if the epoch is received as a parameter and needs to be validated (lower or equal with latest epoch). + * @param ajnaPool_ Address of ajna pool associated with the stake. */ function _claimRewards( + StakeInfo storage stakeInfo_, uint256 tokenId_, - uint256 epochToClaim_ + uint256 epochToClaim_, + bool validateEpoch_, + address ajnaPool_ ) internal { - StakeInfo storage stakeInfo = stakes[tokenId_]; - address ajnaPool = stakeInfo.ajnaPool; + // revert if higher epoch to claim than current burn epoch + if (validateEpoch_ && epochToClaim_ > IPool(ajnaPool_).currentBurnEpoch()) revert EpochNotAvailable(); // update bucket exchange rates and claim associated rewards uint256 rewardsEarned = _updateBucketExchangeRates( - ajnaPool, + ajnaPool_, positionManager.getPositionIndexes(tokenId_) ); rewardsEarned += _calculateAndClaimRewards(tokenId_, epochToClaim_); uint256[] memory burnEpochsClaimed = _getBurnEpochsClaimed( - stakeInfo.lastInteractionBurnEpoch, + stakeInfo_.lastInteractionBurnEpoch, epochToClaim_ ); emit ClaimRewards( msg.sender, - ajnaPool, + ajnaPool_, tokenId_, burnEpochsClaimed, rewardsEarned ); // update last interaction burn event - stakeInfo.lastInteractionBurnEpoch = uint96(epochToClaim_); - - uint256 ajnaBalance = IERC20(ajnaToken).balanceOf(address(this)); - - if (rewardsEarned > ajnaBalance) rewardsEarned = ajnaBalance; + stakeInfo_.lastInteractionBurnEpoch = uint96(epochToClaim_); // transfer rewards to sender - IERC20(ajnaToken).safeTransfer(msg.sender, rewardsEarned); + _transferAjnaRewards(rewardsEarned); } /** @@ -688,6 +720,20 @@ contract RewardsManager is IRewardsManager { } } + /** @notice Utility method to transfer Ajna rewards to the sender + * @dev This method is used to transfer rewards to the sender after a successful claim or update. + * @dev It is used to ensure that rewards claimers will be able to claim some portion of the remaining tokens if a claim would exceed the remaining contract balance. + * @param rewardsEarned_ Amount of rewards earned by the caller. + */ + function _transferAjnaRewards(uint256 rewardsEarned_) internal { + // check that rewards earned isn't greater than remaining balance + // if remaining balance is greater, set to remaining balance + uint256 ajnaBalance = IERC20(ajnaToken).balanceOf(address(this)); + if (rewardsEarned_ > ajnaBalance) rewardsEarned_ = ajnaBalance; + // transfer rewards to sender + IERC20(ajnaToken).safeTransfer(msg.sender, rewardsEarned_); + } + /************************/ /*** Helper Functions ***/ /************************/ diff --git a/src/base/FlashloanablePool.sol b/src/base/FlashloanablePool.sol index 1beba296d..22e70da69 100644 --- a/src/base/FlashloanablePool.sol +++ b/src/base/FlashloanablePool.sol @@ -2,6 +2,9 @@ pragma solidity 0.8.14; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + import { Pool } from './Pool.sol'; import { IERC3156FlashBorrower } from '../interfaces/pool/IERC3156FlashBorrower.sol'; @@ -12,36 +15,45 @@ import { IERC3156FlashBorrower } from '../interfaces/pool/IERC3156FlashBorrower. * @notice Flash loans can be taking in ERC20 quote and ERC20 collateral tokens. */ abstract contract FlashloanablePool is Pool { + using SafeERC20 for IERC20; + /** * @notice Called by flashloan borrowers to borrow liquidity which must be repaid in the same transaction. * @param receiver_ Address of the contract which implements the appropriate interface to receive tokens. * @param token_ Address of the ERC20 token caller wants to borrow. * @param amount_ The amount of tokens to borrow. * @param data_ User-defined calldata passed to the receiver. - * @return True if successful. + * @return success_ True if flashloan was successful. */ function flashLoan( IERC3156FlashBorrower receiver_, address token_, uint256 amount_, bytes calldata data_ - ) external virtual override nonReentrant returns (bool) { - if (token_ == _getArgAddress(QUOTE_ADDRESS)) return _flashLoanQuoteToken(receiver_, token_, amount_, data_); - revert FlashloanUnavailableForToken(); - } + ) external virtual override nonReentrant returns (bool success_) { + if (!_isFlashloanSupported(token_)) revert FlashloanUnavailableForToken(); + + IERC20 tokenContract = IERC20(token_); + + uint256 initialBalance = tokenContract.balanceOf(address(this)); + + tokenContract.safeTransfer( + address(receiver_), + amount_ + ); - function _flashLoanQuoteToken(IERC3156FlashBorrower receiver_, - address token_, - uint256 amount_, - bytes calldata data_ - ) internal returns (bool) { - _transferQuoteToken(address(receiver_), amount_); - if (receiver_.onFlashLoan(msg.sender, token_, amount_, 0, data_) != keccak256("ERC3156FlashBorrower.onFlashLoan")) revert FlashloanCallbackFailed(); - _transferQuoteTokenFrom(address(receiver_), amount_); - return true; + tokenContract.safeTransferFrom( + address(receiver_), + address(this), + amount_ + ); + + if (tokenContract.balanceOf(address(this)) != initialBalance) revert FlashloanIncorrectBalance(); + + success_ = true; } /** @@ -51,7 +63,7 @@ abstract contract FlashloanablePool is Pool { address token_, uint256 ) external virtual view override returns (uint256) { - if (token_ != _getArgAddress(QUOTE_ADDRESS)) revert FlashloanUnavailableForToken(); + if (!_isFlashloanSupported(token_)) revert FlashloanUnavailableForToken(); return 0; } @@ -63,6 +75,18 @@ abstract contract FlashloanablePool is Pool { function maxFlashLoan( address token_ ) external virtual view override returns (uint256 maxLoan_) { - if (token_ == _getArgAddress(QUOTE_ADDRESS)) maxLoan_ = _getPoolQuoteTokenBalance(); + if (_isFlashloanSupported(token_)) maxLoan_ = IERC20(token_).balanceOf(address(this)); + } + + /** + * @notice Returns true if pool allows flashloans for given token address, false otherwise. + * @dev Allows flashloans for quote token, overriden in pool implementation to allow flashloans for other tokens. + * @param token_ Address of the ERC20 token to be lent. + * @return True if token can be flashloaned, false otherwise. + */ + function _isFlashloanSupported( + address token_ + ) internal virtual view returns (bool) { + return token_ == _getArgAddress(QUOTE_ADDRESS); } } \ No newline at end of file diff --git a/src/base/PermitERC721.sol b/src/base/PermitERC721.sol index a8f3a87af..41084f410 100644 --- a/src/base/PermitERC721.sol +++ b/src/base/PermitERC721.sol @@ -109,7 +109,6 @@ abstract contract PermitERC721 is ERC721, IPermit { * @notice Called by an NFT owner to enable their NFT to be transferred by a spender address without making a seperate approve call * @param from_ The address of the current owner of the NFT * @param to_ The address of the new owner of the NFT - * @param spender_ The address of the third party who will execute the transaction involving an owners NFT * @param tokenId_ The id of the NFT being interacted with * @param deadline_ The unix timestamp by which the permit must be called * @param v_ Component of secp256k1 signature @@ -117,9 +116,9 @@ abstract contract PermitERC721 is ERC721, IPermit { * @param s_ Component of secp256k1 signature */ function safeTransferFromWithPermit( - address from_, address to_, address spender_, uint256 tokenId_, uint256 deadline_, uint8 v_, bytes32 r_, bytes32 s_ + address from_, address to_, uint256 tokenId_, uint256 deadline_, uint8 v_, bytes32 r_, bytes32 s_ ) external { - this.permit(spender_, tokenId_, deadline_, v_, r_, s_); + this.permit(msg.sender, tokenId_, deadline_, v_, r_, s_); safeTransferFrom(from_, to_, tokenId_); } diff --git a/src/base/Pool.sol b/src/base/Pool.sol index f51c77f5f..36a7a7e84 100644 --- a/src/base/Pool.sol +++ b/src/base/Pool.sol @@ -45,7 +45,8 @@ import { } from '../libraries/helpers/PoolHelper.sol'; import { _revertIfAuctionDebtLocked, - _revertIfAuctionClearable + _revertIfAuctionClearable, + _revertOnExpiry } from '../libraries/helpers/RevertsHelper.sol'; import { Buckets } from '../libraries/internal/Buckets.sol'; @@ -93,7 +94,7 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { bool internal isPoolInitialized; - mapping(address => mapping(address => mapping(uint256 => uint256))) private _lpTokenAllowances; // owner address -> new owner address -> deposit index -> allowed amount + mapping(address => mapping(address => mapping(uint256 => uint256))) private _lpAllowances; // owner address -> new owner address -> deposit index -> allowed amount /******************/ /*** Immutables ***/ @@ -131,8 +132,10 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { /// @inheritdoc IPoolLenderActions function addQuoteToken( uint256 quoteTokenAmountToAdd_, - uint256 index_ + uint256 index_, + uint256 expiry_ ) external override nonReentrant returns (uint256 bucketLPs_) { + _revertOnExpiry(expiry_); PoolState memory poolState = _accruePoolInterest(); // round to token precision @@ -159,22 +162,24 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { /** * @inheritdoc IPoolLenderActions * @dev write state: - * - _lpTokenAllowances mapping + * - _lpAllowances mapping */ function approveLpOwnership( - address allowedNewOwner_, + address newOwner, uint256 index_, - uint256 lpsAmountToApprove_ + uint256 amount_ ) external nonReentrant { - _lpTokenAllowances[msg.sender][allowedNewOwner_][index_] = lpsAmountToApprove_; + _lpAllowances[msg.sender][newOwner][index_] = amount_; } /// @inheritdoc IPoolLenderActions function moveQuoteToken( uint256 maxAmountToMove_, uint256 fromIndex_, - uint256 toIndex_ + uint256 toIndex_, + uint256 expiry_ ) external override nonReentrant returns (uint256 fromBucketLPs_, uint256 toBucketLPs_) { + _revertOnExpiry(expiry_); PoolState memory poolState = _accruePoolInterest(); _revertIfAuctionDebtLocked(deposits, poolBalances, fromIndex_, poolState.inflator); @@ -242,7 +247,7 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { ) external override nonReentrant { LenderActions.transferLPs( buckets, - _lpTokenAllowances, + _lpAllowances, owner_, newOwner_, indexes_ @@ -273,11 +278,11 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { ); // update pool balances state + poolBalances.t0Debt = result.t0PoolDebt; poolBalances.t0DebtInAuction += result.t0KickedDebt; - poolBalances.t0Debt += result.t0KickPenalty; // update pool interest rate state - poolState.debt += result.kickPenalty; + poolState.debt = Maths.wmul(result.t0PoolDebt, poolState.inflator); _updateInterestState(poolState, result.lup); if(result.amountToCoverBond != 0) _transferQuoteTokenFrom(msg.sender, result.amountToCoverBond); @@ -304,11 +309,11 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { ); // update pool balances state - poolBalances.t0Debt += result.t0KickPenalty; + poolBalances.t0Debt = result.t0PoolDebt; poolBalances.t0DebtInAuction += result.t0KickedDebt; // update pool interest rate state - poolState.debt += result.kickPenalty; + poolState.debt = Maths.wmul(result.t0PoolDebt, poolState.inflator); _updateInterestState(poolState, result.lup); // transfer from kicker to pool the difference to cover bond @@ -320,10 +325,10 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { * @dev write state: * - reset kicker's claimable accumulator */ - function withdrawBonds() external { + function withdrawBonds(address recipient_) external { uint256 claimable = auctions.kickers[msg.sender].claimable; auctions.kickers[msg.sender].claimable = 0; - _transferQuoteToken(msg.sender, claimable); + _transferQuoteToken(recipient_, claimable); } /*********************************/ @@ -356,8 +361,8 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { reserveAuction, StartReserveAuctionParams({ poolSize: Deposits.treeSum(deposits), - poolDebt: poolBalances.t0Debt, - poolBalance: _getPoolQuoteTokenBalance(), + t0PoolDebt: poolBalances.t0Debt, + poolBalance: _getNormalizedPoolQuoteTokenBalance(), inflator: inflatorState.inflator }) ); @@ -424,10 +429,7 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { * @return poolState_ Struct containing pool details. */ function _accruePoolInterest() internal returns (PoolState memory poolState_) { - // retrieve t0Debt amount from poolBalances struct - uint256 t0Debt = poolBalances.t0Debt; - - // initialize fields of poolState_ struct with initial values + poolState_.t0Debt = poolBalances.t0Debt; poolState_.collateral = poolBalances.pledgedCollateral; poolState_.inflator = inflatorState.inflator; poolState_.rate = interestState.interestRate; @@ -435,9 +437,9 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { poolState_.quoteDustLimit = _getArgUint256(QUOTE_SCALE); // check if t0Debt is not equal to 0, indicating that there is debt to be tracked for the pool - if (t0Debt != 0) { + if (poolState_.t0Debt != 0) { // Calculate prior pool debt - poolState_.debt = Maths.wmul(t0Debt, poolState_.inflator); + poolState_.debt = Maths.wmul(poolState_.t0Debt, poolState_.inflator); // calculate elapsed time since inflator was last updated uint256 elapsed = block.timestamp - inflatorState.inflatorUpdate; @@ -455,7 +457,7 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { ); poolState_.inflator = newInflator; // After debt owed to lenders has accrued, calculate current debt owed by borrowers - poolState_.debt = Maths.wmul(t0Debt, poolState_.inflator); + poolState_.debt = Maths.wmul(poolState_.t0Debt, poolState_.inflator); // update total interest earned accumulator with the newly accrued interest reserveAuction.totalInterestEarned += newInterest; @@ -507,8 +509,11 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { IERC20(_getArgAddress(QUOTE_ADDRESS)).safeTransfer(to_, amount_ / _getArgUint256(QUOTE_SCALE)); } - function _getPoolQuoteTokenBalance() internal view returns (uint256) { - return IERC20(_getArgAddress(QUOTE_ADDRESS)).balanceOf(address(this)); + /** + * @dev returns the pool quote token balance normalized to WAD to be used for calculating pool reserves + */ + function _getNormalizedPoolQuoteTokenBalance() internal view returns (uint256) { + return IERC20(_getArgAddress(QUOTE_ADDRESS)).balanceOf(address(this)) * _getArgUint256(QUOTE_SCALE); } function _lup(uint256 debt_) internal view returns (uint256) { @@ -710,4 +715,19 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { reserveAuction.kicked ); } + + /// @inheritdoc IPoolState + function totalAuctionsInPool() external view override returns (uint256) { + return auctions.noOfAuctions; + } + + /// @inheritdoc IPoolState + function totalT0Debt() external view override returns (uint256) { + return poolBalances.t0Debt; + } + + /// @inheritdoc IPoolState + function totalT0DebtInAuction() external view override returns (uint256) { + return poolBalances.t0DebtInAuction; + } } diff --git a/src/base/PoolDeployer.sol b/src/base/PoolDeployer.sol index ffb433a4b..2ae7459c6 100644 --- a/src/base/PoolDeployer.sol +++ b/src/base/PoolDeployer.sol @@ -35,10 +35,9 @@ abstract contract PoolDeployer { * @notice Ensures that pools are deployed according to specifications. * @dev Used by both ERC20, and ERC721 pool factory types. */ - modifier canDeploy(bytes32 subsetHash_, address collateral_, address quote_, uint256 interestRate_) { + modifier canDeploy(address collateral_, address quote_, uint256 interestRate_) { if (collateral_ == address(0) || quote_ == address(0)) revert IPoolFactory.DeployWithZeroAddress(); - if (deployedPools[subsetHash_][collateral_][quote_] != address(0)) revert IPoolFactory.PoolAlreadyExists(); - if (MIN_RATE >= interestRate_ || interestRate_ >= MAX_RATE) revert IPoolFactory.PoolInterestRateInvalid(); + if (MIN_RATE > interestRate_ || interestRate_ > MAX_RATE) revert IPoolFactory.PoolInterestRateInvalid(); _; } diff --git a/src/interfaces/pool/IPool.sol b/src/interfaces/pool/IPool.sol index 2a203fbe9..8ced68587 100644 --- a/src/interfaces/pool/IPool.sol +++ b/src/interfaces/pool/IPool.sol @@ -44,7 +44,7 @@ interface IERC20Token { } interface IERC721Token { - function safeTransferFrom( + function transferFrom( address from, address to, uint256 tokenId diff --git a/src/interfaces/pool/commons/IPoolErrors.sol b/src/interfaces/pool/commons/IPoolErrors.sol index 7641ee9d3..a0803f5f3 100644 --- a/src/interfaces/pool/commons/IPoolErrors.sol +++ b/src/interfaces/pool/commons/IPoolErrors.sol @@ -75,6 +75,11 @@ interface IPoolErrors { */ error FlashloanCallbackFailed(); + /** + * @notice Balance of pool contract before flash loan is different than the balance after flash loan. + */ + error FlashloanIncorrectBalance(); + /** * @notice Pool cannot facilitate a flashloan for the specified token address. */ @@ -105,7 +110,7 @@ interface IPoolErrors { /** * @notice Borrower is attempting to borrow more quote token than is available before the supplied limitIndex. */ - error LimitIndexReached(); + error LimitIndexExceeded(); /** * @notice When moving quote token HTP must stay below LUP. @@ -179,6 +184,16 @@ interface IPoolErrors { */ error TakeNotPastCooldown(); + /** + * @notice Current block timestamp has reached or exceeded a user-provided expiration. + */ + error TransactionExpired(); + + /** + * @notice Owner of the LP tokens attemps to transfer LPs to same address. + */ + error TransferToSameOwner(); + /** * @notice The threshold price of the loan to be inserted in loans heap is zero. */ diff --git a/src/interfaces/pool/commons/IPoolEvents.sol b/src/interfaces/pool/commons/IPoolEvents.sol index f8c7cbf47..49927d9b6 100644 --- a/src/interfaces/pool/commons/IPoolEvents.sol +++ b/src/interfaces/pool/commons/IPoolEvents.sol @@ -211,13 +211,13 @@ interface IPoolEvents { * @param owner The original owner address of the position. * @param newOwner The new owner address of the position. * @param indexes Array of price bucket indexes at which LP tokens were transferred. - * @param lpTokens Amount of LP tokens transferred. + * @param lps Amount of LPs transferred. */ - event TransferLPTokens( + event TransferLPs( address owner, address newOwner, uint256[] indexes, - uint256 lpTokens + uint256 lps ); /** diff --git a/src/interfaces/pool/commons/IPoolInternals.sol b/src/interfaces/pool/commons/IPoolInternals.sol index 197e4ecb2..576f3a721 100644 --- a/src/interfaces/pool/commons/IPoolInternals.sol +++ b/src/interfaces/pool/commons/IPoolInternals.sol @@ -11,22 +11,22 @@ pragma solidity 0.8.14; /*****************************/ struct BucketTakeResult { - uint256 collateralAmount; - uint256 t0RepayAmount; - uint256 t0DebtPenalty; - uint256 remainingCollateral; - uint256 poolDebt; - uint256 newLup; - uint256 t0DebtInAuctionChange; - bool settledAuction; + uint256 collateralAmount; // [WAD] amount of collateral taken + uint256 compensatedCollateral; // [WAD] amount of borrower collateral that is compensated with LPs + uint256 t0DebtPenalty; // [WAD] t0 penalty applied on first take + uint256 remainingCollateral; // [WAD] amount of borrower collateral remaining after take + uint256 poolDebt; // [WAD] current pool debt + uint256 t0PoolDebt; // [WAD] t0 pool debt + uint256 newLup; // [WAD] current lup + uint256 t0DebtInAuctionChange; // [WAD] the amount of t0 debt recovered by take action + bool settledAuction; // true if auction is settled by take action } struct KickResult { - uint256 amountToCoverBond; // amount of bond that needs to be covered - uint256 kickPenalty; // kick penalty - uint256 t0KickPenalty; // t0 kick penalty - uint256 t0KickedDebt; // new t0 debt after kick - uint256 lup; // current lup + uint256 amountToCoverBond; // [WAD] amount of bond that needs to be covered + uint256 t0PoolDebt; // [WAD] t0 debt in pool after kick + uint256 t0KickedDebt; // [WAD] new t0 debt after kick + uint256 lup; // [WAD] current lup } struct SettleParams { @@ -38,16 +38,17 @@ struct SettleParams { } struct TakeResult { - uint256 collateralAmount; - uint256 quoteTokenAmount; - uint256 t0RepayAmount; - uint256 t0DebtPenalty; - uint256 excessQuoteToken; - uint256 remainingCollateral; - uint256 poolDebt; - uint256 newLup; - uint256 t0DebtInAuctionChange; - bool settledAuction; + uint256 collateralAmount; // [WAD] amount of collateral taken + uint256 compensatedCollateral; // [WAD] amount of borrower collateral that is compensated with LPs + uint256 quoteTokenAmount; // [WAD] amount of quote tokens paid by taker for taken collateral + uint256 t0DebtPenalty; // [WAD] t0 penalty applied on first take + uint256 excessQuoteToken; // [WAD] (NFT only) amount of quote tokens to be paid by taker to borrower for fractional collateral + uint256 remainingCollateral; // [WAD] amount of borrower collateral remaining after take + uint256 poolDebt; // [WAD] current pool debt + uint256 t0PoolDebt; // [WAD] t0 pool debt + uint256 newLup; // [WAD] current lup + uint256 t0DebtInAuctionChange; // [WAD] the amount of t0 debt recovered by take action + bool settledAuction; // true if auction is settled by take action } /******************************************/ @@ -83,7 +84,7 @@ struct DrawDebtResult { uint256 remainingCollateral; // [WAD] amount of borrower collateral after draw debt (for NFT can be diminished if auction settled) bool settledAuction; // true if collateral pledged settles auction uint256 t0DebtInAuctionChange; // [WAD] change of t0 pool debt in auction after pledge collateral - uint256 t0DebtChange; // [WAD] change of total t0 pool debt after after draw debt + uint256 t0PoolDebt; // [WAD] amount of t0 debt in pool after draw debt } struct RepayDebtResult { @@ -93,6 +94,6 @@ struct RepayDebtResult { uint256 remainingCollateral; // [WAD] amount of borrower collateral after pull collateral bool settledAuction; // true if repay debt settles auction uint256 t0DebtInAuctionChange; // [WAD] change of t0 pool debt in auction after repay debt - uint256 t0RepaidDebt; // [WAD] amount of t0 repaid debt + uint256 t0PoolDebt; // [WAD] amount of t0 debt in pool after repay uint256 quoteTokenToRepay; // [WAD] quote token amount to be transferred from sender to pool } \ No newline at end of file diff --git a/src/interfaces/pool/commons/IPoolLenderActions.sol b/src/interfaces/pool/commons/IPoolLenderActions.sol index 11546147f..d29172751 100644 --- a/src/interfaces/pool/commons/IPoolLenderActions.sol +++ b/src/interfaces/pool/commons/IPoolLenderActions.sol @@ -10,11 +10,13 @@ interface IPoolLenderActions { * @notice Called by lenders to add an amount of credit at a specified price bucket. * @param amount The amount of quote token to be added by a lender. * @param index The index of the bucket to which the quote tokens will be added. + * @param expiry Timestamp after which this TX will revert, preventing inclusion in a block with unfavorable price. * @return lpbChange The amount of LP Tokens changed for the added quote tokens. */ function addQuoteToken( uint256 amount, - uint256 index + uint256 index, + uint256 expiry ) external returns (uint256 lpbChange); /** @@ -35,13 +37,15 @@ interface IPoolLenderActions { * @param maxAmount The maximum amount of quote token to be moved by a lender. * @param fromIndex The bucket index from which the quote tokens will be removed. * @param toIndex The bucket index to which the quote tokens will be added. + * @param expiry Timestamp after which this TX will revert, preventing inclusion in a block with unfavorable price. * @return lpbAmountFrom The amount of LPs moved out from bucket. * @return lpbAmountTo The amount of LPs moved to destination bucket. */ function moveQuoteToken( uint256 maxAmount, uint256 fromIndex, - uint256 toIndex + uint256 toIndex, + uint256 expiry ) external returns (uint256 lpbAmountFrom, uint256 lpbAmountTo); /** diff --git a/src/interfaces/pool/commons/IPoolLiquidationActions.sol b/src/interfaces/pool/commons/IPoolLiquidationActions.sol index 150738f95..8f20c9047 100644 --- a/src/interfaces/pool/commons/IPoolLiquidationActions.sol +++ b/src/interfaces/pool/commons/IPoolLiquidationActions.sol @@ -63,6 +63,7 @@ interface IPoolLiquidationActions { /** * @notice Called by kickers to withdraw their auction bonds (the amount of quote tokens that are not locked in active auctions). + * @param recipient Address to receive claimed bonds amount. */ - function withdrawBonds() external; + function withdrawBonds(address recipient) external; } \ No newline at end of file diff --git a/src/interfaces/pool/commons/IPoolReserveAuctionActions.sol b/src/interfaces/pool/commons/IPoolReserveAuctionActions.sol index 9e28f5fbe..6ceed46dc 100644 --- a/src/interfaces/pool/commons/IPoolReserveAuctionActions.sol +++ b/src/interfaces/pool/commons/IPoolReserveAuctionActions.sol @@ -27,7 +27,7 @@ interface IPoolReserveAuctionActions { struct StartReserveAuctionParams { uint256 poolSize; // [WAD] total deposits in pool (with accrued debt) - uint256 poolDebt; // [WAD] current t0 pool debt + uint256 t0PoolDebt; // [WAD] current t0 pool debt uint256 poolBalance; // [WAD] pool quote token balance uint256 inflator; // [WAD] pool current inflator } \ No newline at end of file diff --git a/src/interfaces/pool/commons/IPoolState.sol b/src/interfaces/pool/commons/IPoolState.sol index d8123d53a..bfda7af77 100644 --- a/src/interfaces/pool/commons/IPoolState.sol +++ b/src/interfaces/pool/commons/IPoolState.sol @@ -222,6 +222,26 @@ interface IPoolState { */ function pledgedCollateral() external view returns (uint256); + /** + * @notice Returns the total number of active auctions in pool + * @return totalAuctions_ number of active auctions. + */ + function totalAuctionsInPool() external view returns (uint256); + + /** + * @notice Returns the `t0Debt` state variable. + * @dev This value should be multiplied by inflator in order to calculate current debt of the pool. + * @return The total t0Debt in the system, in WAD units. + */ + function totalT0Debt() external view returns (uint256); + + /** + * @notice Returns the `t0DebtInAuction` state variable. + * @dev This value should be multiplied by inflator in order to calculate current debt in auction of the pool. + * @return The total t0DebtInAuction in the system, in WAD units. + */ + function totalT0DebtInAuction() external view returns (uint256); + } /*********************/ @@ -250,6 +270,7 @@ struct PoolBalancesState { struct PoolState { uint8 poolType; // pool type, can be ERC20 or ERC721 + uint256 t0Debt; // [WAD] t0 debt in pool uint256 debt; // [WAD] total debt in pool, accrued in current block uint256 collateral; // [WAD] total collateral pledged in pool uint256 inflator; // [WAD] current pool inflator @@ -261,12 +282,12 @@ struct PoolState { /*** Buckets State ***/ struct Lender { - uint256 lps; // [RAY] Lender LP accumulator + uint256 lps; // [WAD] Lender LP accumulator uint256 depositTime; // timestamp of last deposit } struct Bucket { - uint256 lps; // [RAY] Bucket LP accumulator + uint256 lps; // [WAD] Bucket LP accumulator uint256 collateral; // [WAD] Available collateral tokens deposited in the bucket uint256 bankruptcyTime; // Timestamp when bucket become insolvent, 0 if healthy mapping(address => Lender) lenders; // lender address to Lender struct mapping diff --git a/src/interfaces/pool/erc20/IERC20PoolBorrowerActions.sol b/src/interfaces/pool/erc20/IERC20PoolBorrowerActions.sol index dbab099da..d92a95712 100644 --- a/src/interfaces/pool/erc20/IERC20PoolBorrowerActions.sol +++ b/src/interfaces/pool/erc20/IERC20PoolBorrowerActions.sol @@ -29,10 +29,14 @@ interface IERC20PoolBorrowerActions { * @param borrowerAddress_ The borrower whose loan is being interacted with. * @param maxQuoteTokenAmountToRepay_ The amount of quote tokens to repay. * @param collateralAmountToPull_ The amount of collateral to be puled from the pool. + * @param recipient_ The address to receive amount of pulled collateral. + * @param limitIndex_ Ensures LUP has not moved far from state when borrower pulls collateral. */ function repayDebt( address borrowerAddress_, uint256 maxQuoteTokenAmountToRepay_, - uint256 collateralAmountToPull_ + uint256 collateralAmountToPull_, + address recipient_, + uint256 limitIndex_ ) external; } diff --git a/src/interfaces/pool/erc20/IERC20PoolLenderActions.sol b/src/interfaces/pool/erc20/IERC20PoolLenderActions.sol index b43ee2652..ce0831efb 100644 --- a/src/interfaces/pool/erc20/IERC20PoolLenderActions.sol +++ b/src/interfaces/pool/erc20/IERC20PoolLenderActions.sol @@ -9,11 +9,14 @@ interface IERC20PoolLenderActions { /** * @notice Deposit claimable collateral into a specified bucket. - * @param amount Amount of collateral to deposit. - * @param index The bucket index to which collateral will be deposited. + * @param amount Amount of collateral to deposit. + * @param index The bucket index to which collateral will be deposited. + * @param expiry Timestamp after which this TX will revert, preventing inclusion in a block with unfavorable price. + * @return lpbChange The amount of LP Tokens changed for the added collateral. */ function addCollateral( uint256 amount, - uint256 index + uint256 index, + uint256 expiry ) external returns (uint256 lpbChange); } \ No newline at end of file diff --git a/src/interfaces/pool/erc721/IERC721NonStandard.sol b/src/interfaces/pool/erc721/IERC721NonStandard.sol deleted file mode 100644 index eb66243bf..000000000 --- a/src/interfaces/pool/erc721/IERC721NonStandard.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.14; - -interface ICryptoKitties { - function transferFrom(address from_, address to_, uint256 tokenId_) external; - function transfer(address to_, uint256 tokenId_) external; - function approve(address to_, uint256 tokenId_) external; - function ownerOf(uint256 tokenId_) external returns(address); - function kittyIndexToApproved(uint256 tokenId_) external returns(address); -} - -interface ICryptoPunks { - function buyPunk(uint punkIndex) external; - function transferPunk(address to, uint punkIndex) external; - function offerPunkForSaleToAddress(uint punkIndex, uint minSalePriceInWei, address toAddress) external; - function punkIndexToAddress(uint punkIndex) external returns(address); -} - -enum NFTTypes{ STANDARD_ERC721, CRYPTOPUNKS, CRYPTOKITTIES } \ No newline at end of file diff --git a/src/interfaces/pool/erc721/IERC721PoolBorrowerActions.sol b/src/interfaces/pool/erc721/IERC721PoolBorrowerActions.sol index 5f8e36c95..9206c9b5e 100644 --- a/src/interfaces/pool/erc721/IERC721PoolBorrowerActions.sol +++ b/src/interfaces/pool/erc721/IERC721PoolBorrowerActions.sol @@ -29,10 +29,14 @@ interface IERC721PoolBorrowerActions { * @param borrowerAddress_ The borrower whose loan is being interacted with. * @param maxQuoteTokenAmountToRepay_ The amount of quote tokens to repay. * @param noOfNFTsToPull_ The integer number of NFT collateral to be puled from the pool. + * @param recipient_ The address to receive amount of pulled collateral. + * @param limitIndex_ Ensures LUP has not moved far from state when borrower pulls collateral. */ function repayDebt( address borrowerAddress_, uint256 maxQuoteTokenAmountToRepay_, - uint256 noOfNFTsToPull_ + uint256 noOfNFTsToPull_, + address recipient_, + uint256 limitIndex_ ) external; } diff --git a/src/interfaces/pool/erc721/IERC721PoolFactory.sol b/src/interfaces/pool/erc721/IERC721PoolFactory.sol index 52bfde301..30d842cbf 100644 --- a/src/interfaces/pool/erc721/IERC721PoolFactory.sol +++ b/src/interfaces/pool/erc721/IERC721PoolFactory.sol @@ -10,6 +10,15 @@ import { IPoolFactory } from '../IPoolFactory.sol'; */ interface IERC721PoolFactory is IPoolFactory { + /**************/ + /*** Errors ***/ + /**************/ + + /** + * @notice User tried to deploy a pool with an array of tokenIds that weren't sorted, or contained duplicates. + */ + error TokenIdSubsetInvalid(); + /**************************/ /*** External Functions ***/ /**************************/ diff --git a/src/interfaces/pool/erc721/IERC721PoolLenderActions.sol b/src/interfaces/pool/erc721/IERC721PoolLenderActions.sol index 1c07f06ec..cf068b951 100644 --- a/src/interfaces/pool/erc721/IERC721PoolLenderActions.sol +++ b/src/interfaces/pool/erc721/IERC721PoolLenderActions.sol @@ -11,10 +11,13 @@ interface IERC721PoolLenderActions { * @notice Deposit claimable collateral into a specified bucket. * @param tokenIds Array of collateral to deposit. * @param index The bucket index to which collateral will be deposited. + * @param expiry Timestamp after which this TX will revert, preventing inclusion in a block with unfavorable price. + * @return lpbChange The amount of LP Tokens changed for the added collateral. */ function addCollateral( uint256[] calldata tokenIds, - uint256 index + uint256 index, + uint256 expiry ) external returns (uint256); /** diff --git a/src/interfaces/position/IPositionManagerDerivedState.sol b/src/interfaces/position/IPositionManagerDerivedState.sol index a7e7614a2..a2123b174 100644 --- a/src/interfaces/position/IPositionManagerDerivedState.sol +++ b/src/interfaces/position/IPositionManagerDerivedState.sol @@ -8,16 +8,16 @@ pragma solidity 0.8.14; interface IPositionManagerDerivedState { /** - * @notice Returns the lpTokens accrued to a given tokenId, bucket pairing. + * @notice Returns the LPs accrued to a given tokenId, bucket pairing. * @dev Nested mappings aren't returned normally as part of the default getter for a mapping. - * @param tokenId Unique ID of token. - * @param index Index of bucket to check LP balance of. - * @return lpTokens Balance of lpTokens in the bucket for this position. + * @param tokenId Unique ID of token. + * @param index Index of bucket to check LP balance of. + * @return lps Balance of lps in the bucket for this position. */ - function getLPTokens( + function getLPs( uint256 tokenId, uint256 index - ) external view returns (uint256 lpTokens); + ) external view returns (uint256 lps); /** * @notice Returns an array of bucket indexes in which an NFT has liquidity. diff --git a/src/interfaces/position/IPositionManagerOwnerActions.sol b/src/interfaces/position/IPositionManagerOwnerActions.sol index 70b3ee84d..60e7e6110 100644 --- a/src/interfaces/position/IPositionManagerOwnerActions.sol +++ b/src/interfaces/position/IPositionManagerOwnerActions.sol @@ -9,7 +9,7 @@ interface IPositionManagerOwnerActions { /** * @notice Called by owners to burn an existing NFT. - * @dev Requires that all lp tokens have been removed from the NFT prior to calling. + * @dev Requires that all lps have been removed from the NFT prior to calling. * @param params Calldata struct supplying inputs required to update the underlying assets owed to an NFT. */ function burn( @@ -29,7 +29,8 @@ interface IPositionManagerOwnerActions { ) external; /** - * @notice Called by owners to add quote tokens and receive a representative NFT. + * @notice Called by owners to mint and receive an Ajna Position NFT. + * @dev PositionNFTs can only be minited with an association to pools that have been deployed by the Ajna ERC20PoolFactory or ERC721PoolFactory. * @param params Calldata struct supplying inputs required to mint a positions NFT. * @return tokenId The tokenId of the newly minted NFT. */ @@ -94,6 +95,7 @@ interface IPositionManagerOwnerActions { address pool; // The pool address associated with positions NFT uint256 fromIndex; // The bucket index from which liquidity should be moved uint256 toIndex; // The bucket index to which liquidity should be moved + uint256 expiry; // Timestamp after which this TX will revert, preventing inclusion in a block with unfavorable price } /** @@ -104,4 +106,4 @@ interface IPositionManagerOwnerActions { address pool; // The pool address associated with positions NFT uint256[] indexes; // The array of bucket indexes to reedem positions for } -} \ No newline at end of file +} diff --git a/src/interfaces/rewards/IRewardsManagerErrors.sol b/src/interfaces/rewards/IRewardsManagerErrors.sol index f02a09017..f9f1bb33d 100644 --- a/src/interfaces/rewards/IRewardsManagerErrors.sol +++ b/src/interfaces/rewards/IRewardsManagerErrors.sol @@ -11,6 +11,11 @@ interface IRewardsManagerErrors { */ error AlreadyClaimed(); + /** + * @notice User attempted to claim rewards for an epoch that is not yet available. + */ + error EpochNotAvailable(); + /** * @notice User attempted to record updated exchange rates outside of the allowed period. */ diff --git a/src/interfaces/rewards/IRewardsManagerState.sol b/src/interfaces/rewards/IRewardsManagerState.sol index 01ebd8a3b..0367f3cab 100644 --- a/src/interfaces/rewards/IRewardsManagerState.sol +++ b/src/interfaces/rewards/IRewardsManagerState.sol @@ -47,6 +47,18 @@ interface IRewardsManagerState { uint256 tokenId ) external view returns (address, address, uint256); + /** + * @notice Retrieve information about recorded LPs and rate values for a given bucket and a given stake, at stake time. + * @param tokenId ID of the NFT staked in the rewards contract to retrieve information about. + * @param bucketId ID of the bucket to retrieve recorded information at stake time. + * @return [WAD] LP amount the NFT owner is entitled in current bucket at the time of staking. + * @return [WAD] current bucket exchange rate at the time of staking. + */ + function getBucketStateStakeInfo( + uint256 tokenId, + uint256 bucketId + ) external view returns (uint256, uint256); + } /*********************/ @@ -62,6 +74,6 @@ struct StakeInfo { } struct BucketState { - uint256 lpsAtStakeTime; // [RAY] LP amount the NFT owner is entitled in current bucket at the time of staking - uint256 rateAtStakeTime; // [RAY] current bucket exchange rate at the time of staking (RAY) + uint256 lpsAtStakeTime; // [WAD] LP amount the NFT owner is entitled in current bucket at the time of staking + uint256 rateAtStakeTime; // [WAD] current bucket exchange rate at the time of staking } diff --git a/src/libraries/external/Auctions.sol b/src/libraries/external/Auctions.sol index cd7cc022b..16153864a 100644 --- a/src/libraries/external/Auctions.sol +++ b/src/libraries/external/Auctions.sol @@ -59,17 +59,13 @@ library Auctions { struct BucketTakeParams { address borrower; // borrower address to take from - uint256 collateral; // [WAD] borrower available collateral to take bool depositTake; // deposit or arb take, used by bucket take uint256 index; // bucket index, used by bucket take uint256 inflator; // [WAD] current pool inflator - uint256 t0Debt; // [WAD] borrower t0 debt uint256 collateralScale; // precision of collateral token based on decimals } struct TakeParams { address borrower; // borrower address to take from - uint256 collateral; // [WAD] borrower available collateral to take - uint256 t0Debt; // [WAD] borrower t0 debt uint256 takeCollateral; // [WAD] desired amount to take uint256 inflator; // [WAD] current pool inflator uint256 poolType; // pool type (ERC20 or NFT) @@ -84,13 +80,13 @@ library Auctions { uint256 amountToDebitFromDeposit; // [WAD] the amount of quote tokens used to kick and debited from lender deposit uint256 bucketCollateral; // [WAD] amount of collateral in bucket uint256 bucketDeposit; // [WAD] amount of quote tokens in bucket - uint256 bucketLPs; // [RAY] LPs of the bucket + uint256 bucketLPs; // [WAD] LPs of the bucket uint256 bucketPrice; // [WAD] bucket price - uint256 bucketRate; // [RAY] bucket exchange rate + uint256 bucketRate; // [WAD] bucket exchange rate uint256 bucketScale; // [WAD] bucket scales uint256 bucketUnscaledDeposit; // [WAD] unscaled amount of quote tokens in bucket - uint256 lenderLPs; // [RAY] LPs of lender in bucket - uint256 redeemedLPs; // [RAY] LPs used by kick action + uint256 lenderLPs; // [WAD] LPs of lender in bucket + uint256 redeemedLPs; // [WAD] LPs used by kick action } struct SettleLocalVars { uint256 collateralUsed; // [WAD] collateral used to settle debt @@ -122,20 +118,6 @@ library Auctions { uint256 unscaledDeposit; // [WAD] Unscaled bucket quantity uint256 unscaledQuoteTokenAmount; // [WAD] The unscaled token amount that taker should pay for collateral taken. } - struct TakeLoanLocalVars { - uint256 repaidDebt; // [WAD] the amount of debt repaid to th epool by take auction - uint256 borrowerDebt; // [WAD] the amount of borrower debt - bool inAuction; // true if loan in auction - } - struct TakeFromLoanLocalVars { - uint256 borrowerDebt; // [WAD] borrower's accrued debt - bool inAuction; // true if loan still in auction after auction is taken, false otherwise - uint256 newLup; // [WAD] LUP after auction is taken - uint256 repaidDebt; // [WAD] debt repaid when auction is taken - uint256 t0DebtInAuction; // [WAD] t0 pool debt in auction - uint256 t0DebtInAuctionChange; // [WAD] t0 change amount of debt after auction is taken - uint256 t0PoolDebt; // [WAD] t0 pool debt - } /**************/ /*** Events ***/ @@ -162,6 +144,7 @@ library Auctions { error AuctionNotClearable(); error AuctionPriceGtBucketPrice(); error BorrowerOk(); + error CollateralRoundingNeededButNotPossible(); error InsufficientLiquidity(); error InsufficientCollateral(); error NoAuction(); @@ -192,7 +175,6 @@ library Auctions { * - BucketBankruptcy * @param params_ Settle params * @return collateralRemaining_ The amount of borrower collateral left after settle. - * @return t0DebtRemaining_ The amount of t0 debt left after settle. * @return collateralSettled_ The amount of collateral settled. * @return t0DebtSettled_ The amount of t0 debt settled. */ @@ -204,7 +186,6 @@ library Auctions { SettleParams memory params_ ) external returns ( uint256 collateralRemaining_, - uint256 t0DebtRemaining_, uint256 collateralSettled_, uint256 t0DebtSettled_ ) { @@ -312,14 +293,13 @@ library Auctions { } } - t0DebtRemaining_ = borrower.t0Debt; - t0DebtSettled_ -= t0DebtRemaining_; + t0DebtSettled_ -= borrower.t0Debt; emit Settle(params_.borrower, t0DebtSettled_); if (borrower.t0Debt == 0) { // settle auction - borrower.collateral = _settleAuction( + (borrower.collateral, ) = _settleAuction( auctions_, buckets_, deposits_, @@ -379,7 +359,7 @@ library Auctions { DepositsState storage deposits_, mapping(uint256 => Bucket) storage buckets_, LoansState storage loans_, - PoolState memory poolState_, + PoolState calldata poolState_, uint256 index_ ) external returns ( KickResult memory kickResult_ @@ -406,7 +386,7 @@ library Auctions { vars.bucketPrice ); - vars.amountToDebitFromDeposit = Maths.rayToWad(Maths.rmul(vars.lenderLPs, vars.bucketRate)); // calculate amount to remove based on lender LPs in bucket + vars.amountToDebitFromDeposit = Maths.wmul(vars.lenderLPs, vars.bucketRate); // calculate amount to remove based on lender LPs in bucket if (vars.amountToDebitFromDeposit > vars.bucketDeposit) vars.amountToDebitFromDeposit = vars.bucketDeposit; // cap the amount to remove at bucket deposit @@ -444,7 +424,7 @@ library Auctions { Deposits.unscaledRemove(deposits_, index_, vars.bucketUnscaledDeposit); } else { - vars.redeemedLPs = Maths.wrdivr(vars.amountToDebitFromDeposit, vars.bucketRate); + vars.redeemedLPs = Maths.wdiv(vars.amountToDebitFromDeposit, vars.bucketRate); Deposits.unscaledRemove( deposits_, @@ -484,19 +464,20 @@ library Auctions { if (borrower.collateral == 0) revert InsufficientCollateral(); // revert if borrower's collateral is 0 + uint256 t0RepayAmount; + uint256 t0BorrowerDebt; ( result_.collateralAmount, - result_.t0RepayAmount, - borrower.t0Debt, + t0RepayAmount, + t0BorrowerDebt, result_.t0DebtPenalty ) = _takeBucket( auctions_, buckets_, deposits_, + borrower, BucketTakeParams({ borrower: borrowerAddress_, - collateral: borrower.collateral, - t0Debt: borrower.t0Debt, inflator: poolState_.inflator, depositTake: depositTake_, index: index_, @@ -505,16 +486,20 @@ library Auctions { ); borrower.collateral -= result_.collateralAmount; + borrower.t0Debt = t0BorrowerDebt - t0RepayAmount; - if (result_.t0DebtPenalty != 0) { - poolState_.debt += Maths.wmul(result_.t0DebtPenalty, poolState_.inflator); - } + // update pool debt: apply penalty if case + poolState_.t0Debt += result_.t0DebtPenalty; + poolState_.t0Debt -= t0RepayAmount; + poolState_.debt = Maths.wmul(poolState_.t0Debt, poolState_.inflator); + result_.t0PoolDebt = poolState_.t0Debt; + result_.poolDebt = poolState_.debt; ( - result_.poolDebt, result_.newLup, - result_.t0DebtInAuctionChange, - result_.settledAuction + result_.settledAuction, + result_.remainingCollateral, + result_.compensatedCollateral ) = _takeLoan( auctions_, buckets_, @@ -522,9 +507,16 @@ library Auctions { loans_, poolState_, borrower, - borrowerAddress_, - result_.t0RepayAmount + borrowerAddress_ ); + + if (result_.settledAuction) { + // the overall debt in auction change is the total borrower debt exiting auction + result_.t0DebtInAuctionChange = t0BorrowerDebt; + } else { + // the overall debt in auction change is the amount of partially repaid debt + result_.t0DebtInAuctionChange = t0RepayAmount; + } } /** @@ -547,22 +539,28 @@ library Auctions { ) external returns (TakeResult memory result_) { Borrower memory borrower = loans_.borrowers[borrowerAddress_]; - // revert if borrower's collateral is 0 or if maxCollateral to be taken is 0 - if (borrower.collateral == 0 || collateral_ == 0) revert InsufficientCollateral(); + if ( + (collateral_ == 0) || // revert if amount to take is 0 + (poolState_.poolType == uint8(PoolType.ERC721) && borrower.collateral < 1e18) || // revert in case of NFT take when there isn't a full token to be taken + (poolState_.poolType == uint8(PoolType.ERC20) && borrower.collateral == 0) // revert in case of ERC20 take when no collateral to be taken + ) { + revert InsufficientCollateral(); + } + uint256 t0RepayAmount; + uint256 t0BorrowerDebt; ( result_.collateralAmount, result_.quoteTokenAmount, - result_.t0RepayAmount, - borrower.t0Debt, + t0RepayAmount, + t0BorrowerDebt, result_.t0DebtPenalty, result_.excessQuoteToken ) = _take( auctions_, + borrower, TakeParams({ borrower: borrowerAddress_, - collateral: borrower.collateral, - t0Debt: borrower.t0Debt, takeCollateral: collateral_, inflator: poolState_.inflator, poolType: poolState_.poolType, @@ -571,16 +569,20 @@ library Auctions { ); borrower.collateral -= result_.collateralAmount; + borrower.t0Debt = t0BorrowerDebt - t0RepayAmount; - if (result_.t0DebtPenalty != 0) { - poolState_.debt += Maths.wmul(result_.t0DebtPenalty, poolState_.inflator); - } + // update pool debt: apply penalty if case + poolState_.t0Debt += result_.t0DebtPenalty; + poolState_.t0Debt -= t0RepayAmount; + poolState_.debt = Maths.wmul(poolState_.t0Debt, poolState_.inflator); + result_.t0PoolDebt = poolState_.t0Debt; + result_.poolDebt = poolState_.debt; ( - result_.poolDebt, result_.newLup, - result_.t0DebtInAuctionChange, - result_.settledAuction + result_.settledAuction, + result_.remainingCollateral, + result_.compensatedCollateral ) = _takeLoan( auctions_, buckets_, @@ -588,9 +590,16 @@ library Auctions { loans_, poolState_, borrower, - borrowerAddress_, - result_.t0RepayAmount + borrowerAddress_ ); + + if (result_.settledAuction) { + // the overall debt in auction change is the total borrower debt exiting auction + result_.t0DebtInAuctionChange = t0BorrowerDebt; + } else { + // the overall debt in auction change is the amount of partially repaid debt + result_.t0DebtInAuctionChange = t0RepayAmount; + } } /** @@ -611,7 +620,7 @@ library Auctions { uint256 curUnclaimedAuctionReserve = reserveAuction_.unclaimed; uint256 claimable = _claimableReserves( - Maths.wmul(params_.poolDebt, params_.inflator), + Maths.wmul(params_.t0PoolDebt, params_.inflator), params_.poolSize, auctions_.totalBondEscrowed, curUnclaimedAuctionReserve, @@ -670,10 +679,11 @@ library Auctions { * @notice Performs auction settle based on pool type, emits settle event and removes auction from auctions queue. * @dev emit events: * - AuctionNFTSettle or AuctionSettle - * @param borrowerAddress_ Address of the borrower that exits auction. - * @param borrowerCollateral_ Borrower collateral amount before auction exit (in NFT could be fragmented as result of partial takes). - * @param poolType_ Type of the pool (can be ERC20 or NFT). - * @return remainingCollateral_ Collateral remaining after auction is settled (same amount for ERC20 pool, rounded collateral for NFT pool). + * @param borrowerAddress_ Address of the borrower that exits auction. + * @param borrowerCollateral_ Borrower collateral amount before auction exit (in NFT could be fragmented as result of partial takes). + * @param poolType_ Type of the pool (can be ERC20 or NFT). + * @return remainingCollateral_ Collateral remaining after auction is settled (same amount for ERC20 pool, rounded collateral for NFT pool). + * @return compensatedCollateral_ Amount of collateral compensated (NFT settle only), to be deducted from pool pledged collateral accumulator. 0 for ERC20 pools. */ function _settleAuction( AuctionsState storage auctions_, @@ -682,18 +692,38 @@ library Auctions { address borrowerAddress_, uint256 borrowerCollateral_, uint256 poolType_ - ) internal returns (uint256 remainingCollateral_) { + ) internal returns (uint256 remainingCollateral_, uint256 compensatedCollateral_) { + if (poolType_ == uint8(PoolType.ERC721)) { uint256 lps; uint256 bucketIndex; - (remainingCollateral_, lps, bucketIndex) = _settleNFTCollateral( - auctions_, - buckets_, - deposits_, - borrowerAddress_, - borrowerCollateral_ - ); + remainingCollateral_ = (borrowerCollateral_ / Maths.WAD) * Maths.WAD; // floor collateral of borrower + + // if there's fraction of NFTs remaining then reward difference to borrower as LPs in auction price bucket + if (remainingCollateral_ != borrowerCollateral_) { + + // calculate the amount of collateral that should be compensated with LPs + compensatedCollateral_ = borrowerCollateral_ - remainingCollateral_; + + uint256 auctionPrice = _auctionPrice( + auctions_.liquidations[borrowerAddress_].kickMomp, + auctions_.liquidations[borrowerAddress_].neutralPrice, + auctions_.liquidations[borrowerAddress_].kickTime + ); + + // determine the bucket index to compensate fractional collateral + bucketIndex = auctionPrice > MIN_PRICE ? _indexOf(auctionPrice) : MAX_FENWICK_INDEX; + + // deposit collateral in bucket and reward LPs to compensate fractional collateral + lps = Buckets.addCollateral( + buckets_[bucketIndex], + borrowerAddress_, + Deposits.valueAt(deposits_, bucketIndex), + compensatedCollateral_, + _priceAt(bucketIndex) + ); + } emit AuctionNFTSettle(borrowerAddress_, remainingCollateral_, lps, bucketIndex); @@ -706,46 +736,6 @@ library Auctions { _removeAuction(auctions_, borrowerAddress_); } - /** - * @notice Performs NFT collateral settlement by rounding down borrower's collateral amount and by moving borrower's token ids to pool claimable array. - * @param borrowerAddress_ Address of the borrower that exits auction. - * @param borrowerCollateral_ Borrower collateral amount before auction exit (could be fragmented as result of partial takes). - * @return floorCollateral_ Rounded down collateral, the number of NFT tokens borrower can pull after auction exit. - * @return lps_ LPs given to the borrower to compensate fractional collateral (if any). - * @return bucketIndex_ Index of the bucket with LPs to compensate. - */ - function _settleNFTCollateral( - AuctionsState storage auctions_, - mapping(uint256 => Bucket) storage buckets_, - DepositsState storage deposits_, - address borrowerAddress_, - uint256 borrowerCollateral_ - ) internal returns (uint256 floorCollateral_, uint256 lps_, uint256 bucketIndex_) { - floorCollateral_ = (borrowerCollateral_ / Maths.WAD) * Maths.WAD; // floor collateral of borrower - - // if there's fraction of NFTs remaining then reward difference to borrower as LPs in auction price bucket - if (floorCollateral_ != borrowerCollateral_) { - // cover borrower's fractional amount with LPs in auction price bucket - uint256 fractionalCollateral = borrowerCollateral_ - floorCollateral_; - - uint256 auctionPrice = _auctionPrice( - auctions_.liquidations[borrowerAddress_].kickMomp, - auctions_.liquidations[borrowerAddress_].neutralPrice, - auctions_.liquidations[borrowerAddress_].kickTime - ); - - bucketIndex_ = auctionPrice > MIN_PRICE ? _indexOf(auctionPrice) : MAX_FENWICK_INDEX; - - lps_ = Buckets.addCollateral( - buckets_[bucketIndex_], - borrowerAddress_, - Deposits.valueAt(deposits_, bucketIndex_), - fractionalCollateral, - _priceAt(bucketIndex_) - ); - } - } - /** * @notice Called to start borrower liquidation and to update the auctions queue. * @dev write state: @@ -770,7 +760,7 @@ library Auctions { AuctionsState storage auctions_, DepositsState storage deposits_, LoansState storage loans_, - PoolState memory poolState_, + PoolState calldata poolState_, address borrowerAddress_, uint256 additionalDebt_ ) internal returns ( @@ -806,10 +796,6 @@ library Auctions { momp ); - // when loan is kicked, penalty of three months of interest is added - kickResult_.kickPenalty = Maths.wmul(Maths.wdiv(poolState_.rate, 4 * 1e18), borrowerDebt); - kickResult_.t0KickPenalty = Maths.wdiv(kickResult_.kickPenalty, poolState_.inflator); - // record liquidation info uint256 neutralPrice = Maths.wmul(borrower.t0Np, poolState_.inflator); _recordAuction( @@ -827,14 +813,20 @@ library Auctions { // remove kicked loan from heap Loans.remove(loans_, borrowerAddress_, loans_.indices[borrowerAddress_]); - kickResult_.t0KickedDebt += kickResult_.t0KickPenalty; + // when loan is kicked, penalty of three months of interest is added + uint256 t0KickPenalty = Maths.wmul(kickResult_.t0KickedDebt, Maths.wdiv(poolState_.rate, 4 * 1e18)); + uint256 kickPenalty = Maths.wmul(t0KickPenalty, poolState_.inflator); + + kickResult_.t0PoolDebt = poolState_.t0Debt + t0KickPenalty; + kickResult_.t0KickedDebt += t0KickPenalty; + // update borrower debt with kicked debt penalty borrower.t0Debt = kickResult_.t0KickedDebt; emit Kick( borrowerAddress_, - borrowerDebt + kickResult_.kickPenalty, - borrower.collateral, + borrowerDebt + kickPenalty, + borrowerCollateral, bondSize ); } @@ -843,7 +835,8 @@ library Auctions { * @notice Performs take collateral on an auction and updates bond size and kicker balance accordingly. * @dev emit events: * - Take - * @param params_ Struct containing take action params details. + * @param borrower_ Struct containing auctioned borrower details. + * @param params_ Struct containing take action params details. * @return Collateral amount taken. * @return Quote token to be received from taker. * @return T0 debt amount repaid. @@ -853,22 +846,34 @@ library Auctions { */ function _take( AuctionsState storage auctions_, + Borrower memory borrower_, TakeParams memory params_ ) internal returns (uint256, uint256, uint256, uint256, uint256, uint256) { Liquidation storage liquidation = auctions_.liquidations[params_.borrower]; - TakeLocalVars memory vars = _prepareTake(liquidation, params_.t0Debt, params_.collateral, params_.inflator); + TakeLocalVars memory vars = _prepareTake( + liquidation, + borrower_.t0Debt, + borrower_.collateral, + params_.inflator + ); // These are placeholder max values passed to calculateTakeFlows because there is no explicit bound on the // quote token amount in take calls (as opposed to bucketTake) vars.unscaledDeposit = type(uint256).max; vars.bucketScale = Maths.WAD; + uint256 takeableCollateral = borrower_.collateral; + // for NFT take make sure the take flow and bond change calculation happens for the rounded collateral that can be taken + if (params_.poolType == uint8(PoolType.ERC721)) { + takeableCollateral = (takeableCollateral / 1e18) * 1e18; + } + // In the case of take, the taker binds the collateral qty but not the quote token qty // ugly to get take work like a bucket take -- this is the max amount of quote token from the take that could go to // reduce the debt of the borrower -- analagous to the amount of deposit in the bucket for a bucket take vars = _calculateTakeFlowsAndBondChange( - Maths.min(params_.collateral, params_.takeCollateral), + Maths.min(takeableCollateral, params_.takeCollateral), params_.inflator, params_.collateralScale, vars @@ -888,14 +893,18 @@ library Auctions { // slither-disable-next-line divide-before-multiply uint256 collateralTaken = (vars.collateralAmount / 1e18) * 1e18; // solidity rounds down, so if 2.5 it will be 2.5 / 1 = 2 - if (collateralTaken != vars.collateralAmount && params_.collateral >= collateralTaken + 1e18) { // collateral taken not a round number - collateralTaken += 1e18; // round up collateral to take - // taker should send additional quote tokens to cover difference between collateral needed to be taken and rounded collateral, at auction price - // borrower will get quote tokens for the difference between rounded collateral and collateral taken to cover debt - vars.excessQuoteToken = Maths.wmul(collateralTaken - vars.collateralAmount, vars.auctionPrice); + if (collateralTaken != vars.collateralAmount) { // collateral taken not a round number + if (Maths.min(borrower_.collateral, params_.takeCollateral) >= collateralTaken + 1e18) { + collateralTaken += 1e18; // round up collateral to take + // taker should send additional quote tokens to cover difference between collateral needed to be taken and rounded collateral, at auction price + // borrower will get quote tokens for the difference between rounded collateral and collateral taken to cover debt + vars.excessQuoteToken = Maths.wmul(collateralTaken - vars.collateralAmount, vars.auctionPrice); + vars.collateralAmount = collateralTaken; + } else { + // shouldn't get here, but just in case revert + revert CollateralRoundingNeededButNotPossible(); + } } - - vars.collateralAmount = collateralTaken; } return ( @@ -912,7 +921,8 @@ library Auctions { * @notice Performs bucket take collateral on an auction and rewards taker and kicker (if case). * @dev emit events: * - BucketTake - * @param params_ Struct containing take action details. + * @param borrower_ Struct containing auctioned borrower details. + * @param params_ Struct containing take action details. * @return Collateral amount taken. * @return T0 debt amount repaid. * @return T0 borrower debt (including penalty). @@ -922,12 +932,18 @@ library Auctions { AuctionsState storage auctions_, mapping(uint256 => Bucket) storage buckets_, DepositsState storage deposits_, + Borrower memory borrower_, BucketTakeParams memory params_ ) internal returns (uint256, uint256, uint256, uint256) { Liquidation storage liquidation = auctions_.liquidations[params_.borrower]; - TakeLocalVars memory vars = _prepareTake(liquidation, params_.t0Debt, params_.collateral, params_.inflator); + TakeLocalVars memory vars = _prepareTake( + liquidation, + borrower_.t0Debt, + borrower_.collateral, + params_.inflator + ); vars.unscaledDeposit = Deposits.unscaledValueAt(deposits_, params_.index); @@ -944,7 +960,7 @@ library Auctions { vars.bucketScale = Deposits.scale(deposits_, params_.index); vars = _calculateTakeFlowsAndBondChange( - params_.collateral, + borrower_.collateral, params_.inflator, params_.collateralScale, vars @@ -982,13 +998,13 @@ library Auctions { * @notice If borrower becomes recollateralized then auction is settled. Update loan's state. * @dev reverts on: * - borrower debt less than pool min debt AmountLTMinDebt() - * @param borrower_ The borrower details owning loan that is taken. - * @param borrowerAddress_ The address of the borrower. - * @param t0RepaidDebt_ T0 debt amount repaid by the take action. - * @return poolDebt_ Accrued debt pool after debt is repaid. - * @return newLup_ The new LUP of pool (after debt is repaid). - * @return t0DebtInAuctionChange_ The overall debt in auction change (remaining borrower debt if auction settled, repaid debt otherwise). - * @return settledAuction_ True if auction is settled by the take action. + * @param borrower_ Struct containing pool details. + * @param borrower_ The borrower details owning loan that is taken. + * @param borrowerAddress_ The address of the borrower. + * @return newLup_ The new LUP of pool (after debt is repaid). + * @return settledAuction_ True if auction is settled by the take action. (NFT take: rebalance borrower collateral in pool if true) + * @return remainingCollateral_ Borrower collateral remaining after take action. (NFT take: collateral to be rebalanced in case of NFT settlement) + * @return compensatedCollateral_ Amount of collateral compensated, to be deducted from pool pledged collateral accumulator. */ function _takeLoan( AuctionsState storage auctions_, @@ -997,41 +1013,34 @@ library Auctions { LoansState storage loans_, PoolState memory poolState_, Borrower memory borrower_, - address borrowerAddress_, - uint256 t0RepaidDebt_ + address borrowerAddress_ ) internal returns ( - uint256 poolDebt_, uint256 newLup_, - uint256 t0DebtInAuctionChange_, - bool settledAuction_ + bool settledAuction_, + uint256 remainingCollateral_, + uint256 compensatedCollateral_ ) { - TakeLoanLocalVars memory vars; - - vars.repaidDebt = Maths.wmul(t0RepaidDebt_, poolState_.inflator); - vars.borrowerDebt = Maths.wmul(borrower_.t0Debt, poolState_.inflator); - - vars.borrowerDebt -= vars.repaidDebt; - poolDebt_ = poolState_.debt - vars.repaidDebt; + uint256 borrowerDebt = Maths.wmul(borrower_.t0Debt, poolState_.inflator); // check that taking from loan doesn't leave borrower debt under min debt amount - _revertOnMinDebt(loans_, poolDebt_, vars.borrowerDebt, poolState_.quoteDustLimit); - - newLup_ = _lup(deposits_, poolDebt_); + _revertOnMinDebt( + loans_, + poolState_.debt, + borrowerDebt, + poolState_.quoteDustLimit + ); - vars.inAuction = true; + // calculate new lup with repaid debt from take + newLup_ = _lup(deposits_, poolState_.debt); - if (_isCollateralized(vars.borrowerDebt, borrower_.collateral, newLup_, poolState_.poolType)) { - // settle auction if borrower becomes re-collateralized + remainingCollateral_ = borrower_.collateral; - vars.inAuction = false; + if (_isCollateralized(borrowerDebt, borrower_.collateral, newLup_, poolState_.poolType)) { settledAuction_ = true; - // the overall debt in auction change is the total borrower debt exiting auction - t0DebtInAuctionChange_ = borrower_.t0Debt; - // settle auction and update borrower's collateral with value after settlement - borrower_.collateral = _settleAuction( + (remainingCollateral_, compensatedCollateral_) = _settleAuction( auctions_, buckets_, deposits_, @@ -1039,12 +1048,9 @@ library Auctions { borrower_.collateral, poolState_.poolType ); - } else { - // the overall debt in auction change is the amount of partially repaid debt - t0DebtInAuctionChange_ = t0RepaidDebt_; - } - borrower_.t0Debt -= t0RepaidDebt_; + borrower_.collateral = remainingCollateral_; + } // update loan state, stamp borrower t0Np only when exiting from auction Loans.update( @@ -1053,11 +1059,11 @@ library Auctions { deposits_, borrower_, borrowerAddress_, - vars.borrowerDebt, + poolState_.debt, poolState_.rate, newLup_, - vars.inAuction, - !vars.inAuction // stamp borrower t0Np if exiting from auction + !settledAuction_, + settledAuction_ // stamp borrower t0Np if exiting from auction ); } @@ -1146,6 +1152,8 @@ library Auctions { vars.t0RepayAmount = Maths.wdiv(vars.scaledQuoteTokenAmount, inflator_); vars.unscaledQuoteTokenAmount = vars.unscaledDeposit; + vars.scaledQuoteTokenAmount = Maths.wmul(vars.collateralAmount, vars.auctionPrice); + } else if (vars.borrowerDebt <= borrowerCollateralValue) { // borrower debt is constraining factor vars.collateralAmount = _roundToScale(Maths.wdiv(vars.borrowerDebt, borrowerPrice), collateralScale_); @@ -1164,7 +1172,7 @@ library Auctions { } if (vars.isRewarded) { - // take is above neutralPrice, Kicker is rewarded + // take is below neutralPrice, Kicker is rewarded vars.bondChange = Maths.wmul(vars.scaledQuoteTokenAmount, uint256(vars.bpf)); } else { // take is above neutralPrice, Kicker is penalized @@ -1332,11 +1340,12 @@ library Auctions { ) internal { Bucket storage bucket = buckets_[bucketIndex_]; - uint256 bucketExchangeRate = Buckets.getUnscaledExchangeRate( + uint256 scaledDeposit = Maths.wmul(vars.unscaledDeposit, vars.bucketScale); + + uint256 exchangeRate = Buckets.getExchangeRate( bucket.collateral, bucket.lps, - vars.unscaledDeposit, - vars.bucketScale, + scaledDeposit, vars.bucketPrice ); @@ -1345,10 +1354,9 @@ library Auctions { // if arb take - taker is awarded collateral * (bucket price - auction price) worth (in quote token terms) units of LPB in the bucket if (!depositTake_) { - uint256 takerReward = Maths.wmul(vars.collateralAmount, vars.bucketPrice - vars.auctionPrice); - uint256 takerRewardUnscaledQuoteToken = Maths.wdiv(takerReward, vars.bucketScale); + uint256 takerReward = Maths.wmul(vars.collateralAmount, vars.bucketPrice - vars.auctionPrice); - totalLPsReward = Maths.wrdivr(takerRewardUnscaledQuoteToken, bucketExchangeRate); + totalLPsReward = Maths.wdiv(takerReward, exchangeRate); Buckets.addLenderLPs(bucket, bankruptcyTime, msg.sender, totalLPsReward); } @@ -1357,7 +1365,7 @@ library Auctions { // the bondholder/kicker is awarded bond change worth of LPB in the bucket if (vars.isRewarded) { - kickerLPsReward = Maths.wrdivr(Maths.wdiv(vars.bondChange, vars.bucketScale), bucketExchangeRate); + kickerLPsReward = Maths.wdiv(vars.bondChange, exchangeRate); totalLPsReward += kickerLPsReward; Buckets.addLenderLPs(bucket, bankruptcyTime, vars.kicker, kickerLPsReward); diff --git a/src/libraries/external/BorrowerActions.sol b/src/libraries/external/BorrowerActions.sol index 5fb1f70a5..04e5b020e 100644 --- a/src/libraries/external/BorrowerActions.sol +++ b/src/libraries/external/BorrowerActions.sol @@ -20,7 +20,10 @@ import { _priceAt, _isCollateralized } from '../helpers/PoolHelper.sol'; -import { _revertOnMinDebt } from '../helpers/RevertsHelper.sol'; +import { + _revertIfLupDroppedBelowLimit, + _revertOnMinDebt +} from '../helpers/RevertsHelper.sol'; import { Buckets } from '../internal/Buckets.sol'; import { Deposits } from '../internal/Deposits.sol'; @@ -41,16 +44,19 @@ library BorrowerActions { /*************************/ struct DrawDebtLocalVars { - uint256 borrowerDebt; // [WAD] borrower's accrued debt - uint256 debtChange; // [WAD] additional debt resulted from draw debt action - bool inAuction; // true if loan is auctioned - uint256 lupId; // id of new LUP - bool stampT0Np; // true if loan's t0 neutral price should be restamped (when drawing debt or pledge settles auction) + bool borrow; // true if borrow action + uint256 borrowerDebt; // [WAD] borrower's accrued debt + uint256 compensatedCollateral; // [WAD] amount of borrower collateral that is compensated with LPs (NFTs only) + uint256 t0BorrowAmount; // [WAD] t0 amount to borrow + uint256 t0DebtChange; // [WAD] additional t0 debt resulted from draw debt action + bool inAuction; // true if loan is auctioned + bool pledge; // true if pledge action + bool stampT0Np; // true if loan's t0 neutral price should be restamped (when drawing debt or pledge settles auction) } struct RepayDebtLocalVars { uint256 borrowerDebt; // [WAD] borrower's accrued debt + uint256 compensatedCollateral; // [WAD] amount of borrower collateral that is compensated with LPs (NFTs only) bool inAuction; // true if loan still in auction after repay, false otherwise - uint256 newLup; // [WAD] LUP after repay debt action bool pull; // true if pull action bool repay; // true if repay action bool stampT0Np; // true if loan's t0 neutral price should be restamped (when repay settles auction or pull collateral) @@ -63,10 +69,11 @@ library BorrowerActions { /**************/ // See `IPoolErrors` for descriptions + error AuctionActive(); error BorrowerNotSender(); error BorrowerUnderCollateralized(); error InsufficientCollateral(); - error LimitIndexReached(); + error LimitIndexExceeded(); error NoDebt(); /***************************/ @@ -91,7 +98,7 @@ library BorrowerActions { * @dev reverts on: * - borrower not sender BorrowerNotSender() * - borrower debt less than pool min debt AmountLTMinDebt() - * - limit price reached LimitIndexReached() + * - limit price reached LimitIndexExceeded() * - borrower cannot draw more debt BorrowerUnderCollateralized() * @dev emit events: * - Auctions._settleAuction: @@ -112,21 +119,27 @@ library BorrowerActions { ) { Borrower memory borrower = loans_.borrowers[borrowerAddress_]; - result_.poolDebt = poolState_.debt; - result_.newLup = _lup(deposits_, result_.poolDebt); - result_.poolCollateral = poolState_.collateral; - DrawDebtLocalVars memory vars; + vars.pledge = collateralToPledge_ != 0; + vars.borrow = amountToBorrow_ != 0 || limitIndex_ != 0; // enable an intentional 0 borrow loan call to update borrower's loan state vars.borrowerDebt = Maths.wmul(borrower.t0Debt, poolState_.inflator); + vars.inAuction = _inAuction(auctions_, borrowerAddress_); + + result_.t0PoolDebt = poolState_.t0Debt; + result_.poolDebt = poolState_.debt; + result_.poolCollateral = poolState_.collateral; - // pledge collateral to pool - if (collateralToPledge_ != 0) { + result_.remainingCollateral = borrower.collateral; + + if (vars.pledge) { // add new amount of collateral to pledge to borrower balance borrower.collateral += collateralToPledge_; - // load loan's auction state - vars.inAuction = _inAuction(auctions_, borrowerAddress_); + result_.remainingCollateral += collateralToPledge_; + + result_.newLup = _lup(deposits_, result_.poolDebt); + // if loan is auctioned and becomes collateralized by newly pledged collateral then settle auction if ( vars.inAuction && @@ -142,7 +155,10 @@ library BorrowerActions { result_.t0DebtInAuctionChange = borrower.t0Debt; // settle auction and update borrower's collateral with value after settlement - result_.remainingCollateral = Auctions._settleAuction( + ( + result_.remainingCollateral, + vars.compensatedCollateral + ) = Auctions._settleAuction( auctions_, buckets_, deposits_, @@ -151,37 +167,49 @@ library BorrowerActions { poolState_.poolType ); - borrower.collateral = result_.remainingCollateral; + borrower.collateral = result_.remainingCollateral; + result_.poolCollateral -= vars.compensatedCollateral; } // add new amount of collateral to pledge to pool balance result_.poolCollateral += collateralToPledge_; } - // borrow against pledged collateral - // check both values to enable an intentional 0 borrow loan call to update borrower's loan state - if (amountToBorrow_ != 0 || limitIndex_ != 0) { + if (vars.borrow) { // only intended recipient can borrow quote if (borrowerAddress_ != msg.sender) revert BorrowerNotSender(); - // add origination fee to the amount to borrow and add to borrower's debt - vars.debtChange = Maths.wmul(amountToBorrow_, _feeRate(poolState_.rate) + Maths.WAD); + // an auctioned borrower in not allowed to draw more debt (even if collateralized at the new LUP) if auction is not settled + if (vars.inAuction) revert AuctionActive(); + + vars.t0BorrowAmount = Maths.wdiv(amountToBorrow_, poolState_.inflator); + + // t0 debt change is t0 amount to borrow plus the origination fee + vars.t0DebtChange = Maths.wmul(vars.t0BorrowAmount, _feeRate(poolState_.rate) + Maths.WAD); + + borrower.t0Debt += vars.t0DebtChange; - vars.borrowerDebt += vars.debtChange; + vars.borrowerDebt = Maths.wmul(borrower.t0Debt, poolState_.inflator); - // check that drawing debt doesn't leave borrower debt under min debt amount - _revertOnMinDebt(loans_, result_.poolDebt, vars.borrowerDebt, poolState_.quoteDustLimit); + // check that drawing debt doesn't leave borrower debt under pool min debt amount + _revertOnMinDebt( + loans_, + result_.poolDebt, + vars.borrowerDebt, + poolState_.quoteDustLimit + ); // add debt change to pool's debt - result_.poolDebt += vars.debtChange; + result_.t0PoolDebt += vars.t0DebtChange; + result_.poolDebt = Maths.wmul(result_.t0PoolDebt, poolState_.inflator); - // determine new lup index and revert if borrow happens at a price higher than the specified limit (lower index than lup index) - vars.lupId = _lupIndex(deposits_, result_.poolDebt); - if (vars.lupId > limitIndex_) revert LimitIndexReached(); + result_.newLup = _lup(deposits_, result_.poolDebt); + + // revert if borrow drives LUP price under the specified price limit + _revertIfLupDroppedBelowLimit(result_.newLup, limitIndex_); // calculate new lup and check borrow action won't push borrower into a state of under-collateralization // this check also covers the scenario when loan is already auctioned - result_.newLup = _priceAt(vars.lupId); if (!_isCollateralized(vars.borrowerDebt, borrower.collateral, result_.newLup, poolState_.poolType)) { revert BorrowerUnderCollateralized(); @@ -189,10 +217,11 @@ library BorrowerActions { // stamp borrower t0Np when draw debt vars.stampT0Np = true; + } - result_.t0DebtChange = Maths.wdiv(vars.debtChange, poolState_.inflator); - - borrower.t0Debt += result_.t0DebtChange; + // calculate LUP if it wasn't calculated previously + if (!vars.pledge && !vars.borrow) { + result_.newLup = _lup(deposits_, result_.poolDebt); } // update loan state @@ -202,7 +231,7 @@ library BorrowerActions { deposits_, borrower, borrowerAddress_, - vars.borrowerDebt, + result_.poolDebt, poolState_.rate, result_.newLup, vars.inAuction, @@ -230,6 +259,7 @@ library BorrowerActions { * - borrower debt less than pool min debt AmountLTMinDebt() * - borrower not sender BorrowerNotSender() * - not enough collateral to pull InsufficientCollateral() + * - limit price reached LimitIndexExceeded() * @dev emit events: * - Auctions._settleAuction: * - AuctionNFTSettle or AuctionSettle @@ -242,7 +272,8 @@ library BorrowerActions { PoolState calldata poolState_, address borrowerAddress_, uint256 maxQuoteTokenAmountToRepay_, - uint256 collateralAmountToPull_ + uint256 collateralAmountToPull_, + uint256 limitIndex_ ) external returns ( RepayDebtResult memory result_ ) { @@ -251,35 +282,45 @@ library BorrowerActions { RepayDebtLocalVars memory vars; vars.repay = maxQuoteTokenAmountToRepay_ != 0; - vars.pull = collateralAmountToPull_ != 0; + vars.pull = collateralAmountToPull_ != 0; vars.borrowerDebt = Maths.wmul(borrower.t0Debt, poolState_.inflator); + vars.inAuction = _inAuction(auctions_, borrowerAddress_); + result_.t0PoolDebt = poolState_.t0Debt; result_.poolDebt = poolState_.debt; result_.poolCollateral = poolState_.collateral; + result_.remainingCollateral = borrower.collateral; + if (vars.repay) { if (borrower.t0Debt == 0) revert NoDebt(); if (maxQuoteTokenAmountToRepay_ == type(uint256).max) { - result_.t0RepaidDebt = borrower.t0Debt; + vars.t0RepaidDebt = borrower.t0Debt; } else { - result_.t0RepaidDebt = Maths.min( + vars.t0RepaidDebt = Maths.min( borrower.t0Debt, Maths.wdiv(maxQuoteTokenAmountToRepay_, poolState_.inflator) ); } - result_.quoteTokenToRepay = Maths.wmul(result_.t0RepaidDebt, poolState_.inflator); + result_.t0PoolDebt -= vars.t0RepaidDebt; - result_.poolDebt -= result_.quoteTokenToRepay; - vars.borrowerDebt -= result_.quoteTokenToRepay; + result_.poolDebt = Maths.wmul(result_.t0PoolDebt, poolState_.inflator); + result_.quoteTokenToRepay = Maths.wmul(vars.t0RepaidDebt, poolState_.inflator); + vars.borrowerDebt = Maths.wmul(borrower.t0Debt - vars.t0RepaidDebt, poolState_.inflator); // check that paying the loan doesn't leave borrower debt under min debt amount - _revertOnMinDebt(loans_, result_.poolDebt, vars.borrowerDebt, poolState_.quoteDustLimit); + _revertOnMinDebt( + loans_, + result_.poolDebt, + vars.borrowerDebt, + poolState_.quoteDustLimit + ); result_.newLup = _lup(deposits_, result_.poolDebt); - vars.inAuction = _inAuction(auctions_, borrowerAddress_); + // if loan is auctioned and becomes collateralized by repaying debt then settle auction if (vars.inAuction) { if (_isCollateralized(vars.borrowerDebt, borrower.collateral, result_.newLup, poolState_.poolType)) { // borrower becomes re-collateralized @@ -292,7 +333,10 @@ library BorrowerActions { result_.t0DebtInAuctionChange = borrower.t0Debt; // settle auction and update borrower's collateral with value after settlement - result_.remainingCollateral = Auctions._settleAuction( + ( + result_.remainingCollateral, + vars.compensatedCollateral + ) = Auctions._settleAuction( auctions_, buckets_, deposits_, @@ -301,23 +345,29 @@ library BorrowerActions { poolState_.poolType ); - borrower.collateral = result_.remainingCollateral; + borrower.collateral = result_.remainingCollateral; + result_.poolCollateral -= vars.compensatedCollateral; } else { // partial repay, remove only the paid debt from pool auctions debt accumulator - result_.t0DebtInAuctionChange = result_.t0RepaidDebt; + result_.t0DebtInAuctionChange = vars.t0RepaidDebt; } } - borrower.t0Debt -= result_.t0RepaidDebt; + borrower.t0Debt -= vars.t0RepaidDebt; } if (vars.pull) { // only intended recipient can pull collateral if (borrowerAddress_ != msg.sender) revert BorrowerNotSender(); - // calculate LUP only if it wasn't calculated by repay action + // an auctioned borrower in not allowed to pull collateral (even if collateralized at the new LUP) if auction is not settled + if (vars.inAuction) revert AuctionActive(); + + // calculate LUP only if it wasn't calculated in repay action if (!vars.repay) result_.newLup = _lup(deposits_, result_.poolDebt); + _revertIfLupDroppedBelowLimit(result_.newLup, limitIndex_); + uint256 encumberedCollateral = borrower.t0Debt != 0 ? Maths.wdiv(vars.borrowerDebt, result_.newLup) : 0; if (borrower.collateral - encumberedCollateral < collateralAmountToPull_) revert InsufficientCollateral(); @@ -329,7 +379,7 @@ library BorrowerActions { result_.poolCollateral -= collateralAmountToPull_; } - // calculate LUP if repay is called with 0 amount + // calculate LUP if it wasn't calculated previously if (!vars.repay && !vars.pull) { result_.newLup = _lup(deposits_, result_.poolDebt); } @@ -341,7 +391,7 @@ library BorrowerActions { deposits_, borrower, borrowerAddress_, - vars.borrowerDebt, + result_.poolDebt, poolState_.rate, result_.newLup, vars.inAuction, @@ -366,18 +416,11 @@ library BorrowerActions { return auctions_.liquidations[borrower_].kickTime != 0; } - function _lupIndex( - DepositsState storage deposits_, - uint256 debt_ - ) internal view returns (uint256) { - return Deposits.findIndexOfSum(deposits_, debt_); - } - function _lup( DepositsState storage deposits_, uint256 debt_ ) internal view returns (uint256) { - return _priceAt(_lupIndex(deposits_, debt_)); + return _priceAt(Deposits.findIndexOfSum(deposits_, debt_)); } } diff --git a/src/libraries/external/LenderActions.sol b/src/libraries/external/LenderActions.sol index f38bf6ea0..91fbf4d6f 100644 --- a/src/libraries/external/LenderActions.sol +++ b/src/libraries/external/LenderActions.sol @@ -34,19 +34,26 @@ library LenderActions { /*************************/ struct MoveQuoteLocalVars { - uint256 amountToMove; // [WAD] Quote token amount to move between indexes. - uint256 fromBucketPrice; // [WAD] Price of the bucket to move amount from. - uint256 fromBucketLPs; // [RAY] Amount of LPs in the bucket to move amount from. - uint256 fromBucketDepositTime; // Time of lender deposit in the bucket to move amount from. - uint256 toBucketPrice; // [WAD] Price of the bucket to move amount to. - uint256 toBucketBankruptcyTime; // Time the bucket to move amount to was marked as insolvent. - uint256 ptp; // [WAD] Pool Threshold Price. - uint256 htp; // [WAD] Highest Threshold Price. + uint256 amountToMove; // [WAD] Quote token amount to move between indexes. + uint256 fromBucketPrice; // [WAD] Price of the bucket to move amount from. + uint256 fromBucketCollateral; // [WAD] Total amount of collateral in from bucket. + uint256 fromBucketLPs; // [WAD] Total amount of LPs in from bucket. + uint256 fromBucketLenderLPs; // [WAD] Amount of LPs owned by lender in from bucket. + uint256 fromBucketDepositTime; // Time of lender deposit in the bucket to move amount from. + uint256 fromBucketRemainingLPs; // Amount of LPs remaining in from bucket after move. + uint256 fromBucketRemainingDeposit; // Amount of scaled deposit remaining in from bucket after move. + uint256 toBucketPrice; // [WAD] Price of the bucket to move amount to. + uint256 toBucketBankruptcyTime; // Time the bucket to move amount to was marked as insolvent. + uint256 toBucketUnscaledDeposit; // Amount of unscaled deposit in to bucket. + uint256 toBucketDeposit; // Amount of scaled deposit in to bucket. + uint256 toBucketScale; // Scale deposit of to bucket. + uint256 ptp; // [WAD] Pool Threshold Price. + uint256 htp; // [WAD] Highest Threshold Price. } struct RemoveDepositParams { uint256 depositConstraint; // [WAD] Constraint on deposit in quote token. - uint256 lpConstraint; // [RAY] Constraint in LPB terms. - uint256 bucketLPs; // [RAY] Total LPB in the bucket. + uint256 lpConstraint; // [WAD] Constraint in LPB terms. + uint256 bucketLPs; // [WAD] Total LPB in the bucket. uint256 bucketCollateral; // [WAD] Claimable collateral in the bucket. uint256 price; // [WAD] Price of bucket. uint256 index; // Bucket index. @@ -62,7 +69,7 @@ library LenderActions { event BucketBankruptcy(uint256 indexed index, uint256 lpForfeited); event MoveQuoteToken(address indexed lender, uint256 indexed from, uint256 indexed to, uint256 amount, uint256 lpRedeemedFrom, uint256 lpAwardedTo, uint256 lup); event RemoveQuoteToken(address indexed lender, uint256 indexed price, uint256 amount, uint256 lpRedeemed, uint256 lup); - event TransferLPTokens(address owner, address newOwner, uint256[] indexes, uint256 lpTokens); + event TransferLPs(address owner, address newOwner, uint256[] indexes, uint256 lps); /**************/ /*** Errors ***/ @@ -80,6 +87,7 @@ library LenderActions { error InsufficientLiquidity(); error InsufficientCollateral(); error MoveToSamePrice(); + error TransferToSameOwner(); /***************************/ /*** External Functions ***/ @@ -191,6 +199,7 @@ library LenderActions { * - dust amount DustAmountNotExceeded() * - invalid index InvalidIndex() * @dev emit events: + * - BucketBankruptcy * - MoveQuoteToken */ function moveQuoteToken( @@ -218,18 +227,21 @@ library LenderActions { Lender storage fromBucketLender = fromBucket.lenders[msg.sender]; vars.fromBucketPrice = _priceAt(params_.fromIndex); - vars.toBucketPrice = _priceAt(params_.toIndex); + vars.fromBucketCollateral = fromBucket.collateral; + vars.fromBucketLPs = fromBucket.lps; vars.fromBucketDepositTime = fromBucketLender.depositTime; - if (fromBucket.bankruptcyTime < vars.fromBucketDepositTime) vars.fromBucketLPs = fromBucketLender.lps; + vars.toBucketPrice = _priceAt(params_.toIndex); + + if (fromBucket.bankruptcyTime < vars.fromBucketDepositTime) vars.fromBucketLenderLPs = fromBucketLender.lps; - (vars.amountToMove, fromBucketRedeemedLPs_, ) = _removeMaxDeposit( + (vars.amountToMove, fromBucketRedeemedLPs_, vars.fromBucketRemainingDeposit) = _removeMaxDeposit( deposits_, RemoveDepositParams({ depositConstraint: params_.maxAmountToMove, - lpConstraint: vars.fromBucketLPs, - bucketLPs: fromBucket.lps, - bucketCollateral: fromBucket.collateral, + lpConstraint: vars.fromBucketLenderLPs, + bucketLPs: vars.fromBucketLPs, + bucketCollateral: vars.fromBucketCollateral, price: vars.fromBucketPrice, index: params_.fromIndex, dustLimit: poolState_.quoteDustLimit @@ -245,41 +257,53 @@ library LenderActions { } } - uint256 unscaledToBucketDeposit = Deposits.unscaledValueAt(deposits_, params_.toIndex); - uint256 toBucketScale = Deposits.scale(deposits_, params_.toIndex); - uint256 toBucketDeposit = Maths.wmul(toBucketScale, unscaledToBucketDeposit); - vars.toBucketPrice = _priceAt(params_.toIndex); + vars.toBucketUnscaledDeposit = Deposits.unscaledValueAt(deposits_, params_.toIndex); + vars.toBucketScale = Deposits.scale(deposits_, params_.toIndex); + vars.toBucketDeposit = Maths.wmul(vars.toBucketUnscaledDeposit, vars.toBucketScale); + toBucketLPs_ = Buckets.quoteTokensToLPs( toBucket.collateral, toBucket.lps, - toBucketDeposit, + vars.toBucketDeposit, vars.amountToMove, vars.toBucketPrice ); - Deposits.unscaledAdd(deposits_, params_.toIndex, Maths.wdiv(vars.amountToMove, toBucketScale)); + Deposits.unscaledAdd(deposits_, params_.toIndex, Maths.wdiv(vars.amountToMove, vars.toBucketScale)); lup_ = _lup(deposits_, poolState_.debt); vars.htp = Maths.wmul(params_.thresholdPrice, poolState_.inflator); // check loan book's htp against new lup, revert if move drives LUP below HTP - if (params_.fromIndex < params_.toIndex) if(vars.htp > lup_) revert LUPBelowHTP(); + if (params_.fromIndex < params_.toIndex && vars.htp > lup_) revert LUPBelowHTP(); - // update lender LPs balance in from bucket - fromBucketLender.lps -= fromBucketRedeemedLPs_; + // update lender and bucket LPs balance in from bucket + vars.fromBucketRemainingLPs = vars.fromBucketLPs - fromBucketRedeemedLPs_; - // update lender LPs balance and deposit time in target bucket - Lender storage toBucketLender = toBucket.lenders[msg.sender]; + if (vars.fromBucketCollateral == 0 && vars.fromBucketRemainingDeposit == 0 && vars.fromBucketRemainingLPs != 0) { + emit BucketBankruptcy(params_.fromIndex, vars.fromBucketRemainingLPs); + fromBucket.lps = 0; + fromBucket.bankruptcyTime = block.timestamp; + } else { + // update lender and bucket LPs balance + fromBucketLender.lps -= fromBucketRedeemedLPs_; + + fromBucket.lps = vars.fromBucketRemainingLPs; + } - if (vars.toBucketBankruptcyTime >= toBucketLender.depositTime) toBucketLender.lps = toBucketLPs_; - else toBucketLender.lps += toBucketLPs_; + // update lender and bucket LPs balance in target bucket + Lender storage toBucketLender = toBucket.lenders[msg.sender]; + if (vars.toBucketBankruptcyTime >= toBucketLender.depositTime) { + toBucketLender.lps = toBucketLPs_; + } else { + toBucketLender.lps += toBucketLPs_; + } // set deposit time to the greater of the lender's from bucket and the target bucket's last bankruptcy timestamp + 1 so deposit won't get invalidated toBucketLender.depositTime = Maths.max(vars.fromBucketDepositTime, vars.toBucketBankruptcyTime + 1); - // update buckets LPs balance - fromBucket.lps -= fromBucketRedeemedLPs_; - toBucket.lps += toBucketLPs_; + // update bucket LPs balance + toBucket.lps += toBucketLPs_; emit MoveQuoteToken( msg.sender, @@ -320,7 +344,7 @@ library LenderActions { RemoveDepositParams memory removeParams; - if (bucket.bankruptcyTime < lender.depositTime) removeParams.lpConstraint = lender.lps; + if (bucket.bankruptcyTime < depositTime) removeParams.lpConstraint = lender.lps; if (removeParams.lpConstraint == 0) revert NoClaim(); // revert if no LP to claim @@ -331,8 +355,8 @@ library LenderActions { removeParams.index = params_.index; removeParams.dustLimit = poolState_.quoteDustLimit; - uint256 unscaledRemaining; - (removedAmount_, redeemedLPs_, unscaledRemaining) = _removeMaxDeposit( + uint256 scaledRemaining; + (removedAmount_, redeemedLPs_, scaledRemaining) = _removeMaxDeposit( deposits_, removeParams ); @@ -351,16 +375,16 @@ library LenderActions { // check loan book's htp against new lup if (htp > lup_) revert LUPBelowHTP(); - // update lender and bucket LPs balances - lender.lps -= redeemedLPs_; - uint256 lpsRemaining = removeParams.bucketLPs - redeemedLPs_; - if (removeParams.bucketCollateral == 0 && unscaledRemaining == 0 && lpsRemaining != 0) { + if (removeParams.bucketCollateral == 0 && scaledRemaining == 0 && lpsRemaining != 0) { emit BucketBankruptcy(params_.index, lpsRemaining); bucket.lps = 0; bucket.bankruptcyTime = block.timestamp; } else { + // update lender and bucket LPs balances + lender.lps -= redeemedLPs_; + bucket.lps = lpsRemaining; } @@ -375,6 +399,8 @@ library LenderActions { * @dev reverts on: * - not enough collateral InsufficientCollateral() * - insufficient LPs InsufficientLPs() + * @dev emit events: + * - BucketBankruptcy */ function removeCollateral( mapping(uint256 => Bucket) storage buckets_, @@ -388,13 +414,14 @@ library LenderActions { if (amount_ > bucketCollateral) revert InsufficientCollateral(); - uint256 bucketPrice = _priceAt(index_); - uint256 bucketLPs = bucket.lps; + uint256 bucketPrice = _priceAt(index_); + uint256 bucketLPs = bucket.lps; + uint256 bucketDeposit = Deposits.valueAt(deposits_, index_); lpAmount_ = Buckets.collateralToLPs( bucketCollateral, bucketLPs, - Deposits.valueAt(deposits_, index_), + bucketDeposit, amount_, bucketPrice ); @@ -405,12 +432,27 @@ library LenderActions { if (bucket.bankruptcyTime < lender.depositTime) lenderLpBalance = lender.lps; if (lenderLpBalance == 0 || lpAmount_ > lenderLpBalance) revert InsufficientLPs(); - // update lender LPs balance - lender.lps -= lpAmount_; - // update bucket LPs and collateral balance - bucket.lps -= Maths.min(bucketLPs, lpAmount_); - bucket.collateral -= Maths.min(bucketCollateral, amount_); + bucketLPs -= lpAmount_; + + // If clearing out the bucket collateral, ensure it's zeroed out + if (bucketLPs == 0 && bucketDeposit == 0) { + amount_ = bucketCollateral; + } + + bucketCollateral -= Maths.min(bucketCollateral, amount_); + bucket.collateral = bucketCollateral; + + if (bucketCollateral == 0 && bucketDeposit == 0 && bucketLPs != 0) { + emit BucketBankruptcy(index_, bucketLPs); + bucket.lps = 0; + bucket.bankruptcyTime = block.timestamp; + } else { + // update lender LPs balance + lender.lps -= lpAmount_; + + bucket.lps = bucketLPs; + } } /** @@ -507,7 +549,7 @@ library LenderActions { * - invalid index InvalidIndex() * - no allowance NoAllowance() * @dev emit events: - * - TransferLPTokens + * - TransferLPs */ function transferLPs( mapping(uint256 => Bucket) storage buckets_, @@ -516,6 +558,9 @@ library LenderActions { address newOwner_, uint256[] calldata indexes_ ) external { + // revert if new owner address is the same as old owner address + if (owner_ == newOwner_) revert TransferToSameOwner(); + uint256 indexesLength = indexes_.length; uint256 tokensTransferred; @@ -539,7 +584,7 @@ library LenderActions { delete allowances_[owner_][newOwner_][index]; // delete allowance - // move lp tokens to the new owner address + // move lps to the new owner address Lender storage newLender = bucket.lenders[newOwner_]; newLender.lps += transferAmount; @@ -554,7 +599,7 @@ library LenderActions { unchecked { ++i; } } - emit TransferLPTokens(owner_, newOwner_, indexes_, tokensTransferred); + emit TransferLPs(owner_, newOwner_, indexes_, tokensTransferred); } /**************************/ @@ -600,9 +645,13 @@ library LenderActions { collateralAmount_ = Maths.min(maxAmount_, bucketCollateral); // determine how much LP would be required to remove the requested amount - uint256 collateralValue = Maths.wmul(bucketPrice, bucketCollateral); - uint256 lpsForAllCollateral = Maths.rmul(bucketLPs, Maths.wwdivr(collateralValue, collateralValue + bucketDeposit)); - uint256 requiredLPs = Maths.rmul(lpsForAllCollateral, Maths.wwdivr(collateralAmount_, bucketCollateral)); + uint256 requiredLPs = Buckets.collateralToLPs( + bucketCollateral, + bucketLPs, + bucketDeposit, + collateralAmount_, + bucketPrice + ); // limit withdrawal by the lender's LPB if (requiredLPs <= lenderLpBalance) { @@ -610,21 +659,28 @@ library LenderActions { lpAmount_ = requiredLPs; } else { lpAmount_ = lenderLpBalance; - collateralAmount_ = Maths.wmul(Maths.rrdivw(lenderLpBalance,lpsForAllCollateral), bucketCollateral); + collateralAmount_ = Maths.wmul(Maths.wdiv(lenderLpBalance, requiredLPs), collateralAmount_); } - // update lender LPs balance - lender.lps -= lpAmount_; - // update bucket LPs and collateral balance - bucketLPs -= Maths.min(bucketLPs, lpAmount_); + bucketLPs -= Maths.min(bucketLPs, lpAmount_); + + // If clearing out the bucket collateral, ensure it's zeroed out + if (bucketLPs == 0 && bucketDeposit == 0) { + collateralAmount_ = bucketCollateral; + } + bucketCollateral -= Maths.min(bucketCollateral, collateralAmount_); - bucket.collateral = bucketCollateral; + bucket.collateral = bucketCollateral; + if (bucketCollateral == 0 && bucketDeposit == 0 && bucketLPs != 0) { emit BucketBankruptcy(index_, bucketLPs); bucket.lps = 0; bucket.bankruptcyTime = block.timestamp; } else { + // update lender LPs balance + lender.lps -= lpAmount_; + bucket.lps = bucketLPs; } } @@ -635,69 +691,61 @@ library LenderActions { * @dev write state: * - Deposits.unscaledRemove (remove amount in Fenwick tree, from index): * - update values array state - * @return removedAmount_ Amount of scaled deposit removed. - * @return redeemedLPs_ Amount of bucket LPs corresponding for calculated unscaled deposit amount. + * @return removedAmount_ Amount of scaled deposit removed. + * @return redeemedLPs_ Amount of bucket LPs corresponding for calculated scaled deposit amount. + * @return scaledRemaining_ Amount of scaled deposit remaining. */ function _removeMaxDeposit( DepositsState storage deposits_, RemoveDepositParams memory params_ - ) internal returns (uint256 removedAmount_, uint256 redeemedLPs_, uint256 unscaledRemaining_) { + ) internal returns (uint256 removedAmount_, uint256 redeemedLPs_, uint256 scaledRemaining_) { uint256 unscaledDepositAvailable = Deposits.unscaledValueAt(deposits_, params_.index); if (unscaledDepositAvailable == 0) revert InsufficientLiquidity(); // revert if there's no liquidity available to remove uint256 depositScale = Deposits.scale(deposits_, params_.index); - uint256 unscaledExchangeRate = Buckets.getUnscaledExchangeRate( + uint256 scaledDepositAvailable = Maths.wmul(unscaledDepositAvailable, depositScale); + + uint256 exchangeRate = Buckets.getExchangeRate( params_.bucketCollateral, params_.bucketLPs, - unscaledDepositAvailable, - depositScale, + scaledDepositAvailable, params_.price ); // Below is pseudocode explaining the logic behind finding the constrained amount of deposit and LPB - // unscaledRemovedAmount is constrained by the de-scaled maxAmount(in QT), the unscaledDeposit constraint, and - // the lender LPB exchange rate in unscaled deposit-to-LPB for the bucket: - // unscaledRemovedAmount = min ( maxAmount_/scale, unscaledDeposit, lenderLPsBalance*unscaledExchangeRate) - // redeemedLPs_ = min ( maxAmount_/(unscaledExchangeRate*scale), unscaledDeposit/unscaledExchangeRate, lenderLPsBalance) + // scaledRemovedAmount is constrained by the scaled maxAmount(in QT), the scaledDeposit constraint, and + // the lender LPB exchange rate in scaled deposit-to-LPB for the bucket: + // scaledRemovedAmount = min ( maxAmount_, scaledDeposit, lenderLPsBalance*exchangeRate) + // redeemedLPs_ = min ( maxAmount_/scaledExchangeRate, scaledDeposit/exchangeRate, lenderLPsBalance) - uint256 unscaledRemovedAmount; - uint256 unscaledLpConstraint = Maths.rmul(params_.lpConstraint, unscaledExchangeRate); + uint256 scaledLpConstraint = Maths.wmul(params_.lpConstraint, exchangeRate); if ( - params_.depositConstraint < Maths.wmul(unscaledDepositAvailable, depositScale) && - Maths.wwdivr(params_.depositConstraint, depositScale) < unscaledLpConstraint + params_.depositConstraint < scaledDepositAvailable && + params_.depositConstraint < scaledLpConstraint ) { // depositConstraint is binding constraint - unscaledRemovedAmount = Maths.wdiv(params_.depositConstraint, depositScale); - redeemedLPs_ = Maths.wrdivr(unscaledRemovedAmount, unscaledExchangeRate); - } else if (Maths.wadToRay(unscaledDepositAvailable) < unscaledLpConstraint) { - // unscaledDeposit is binding constraint - unscaledRemovedAmount = unscaledDepositAvailable; - redeemedLPs_ = Maths.wrdivr(unscaledRemovedAmount, unscaledExchangeRate); + removedAmount_ = params_.depositConstraint; + redeemedLPs_ = Maths.wdiv(removedAmount_, exchangeRate); + } else if (scaledDepositAvailable < scaledLpConstraint) { + // scaledDeposit is binding constraint + removedAmount_ = scaledDepositAvailable; + redeemedLPs_ = Maths.wdiv(removedAmount_, exchangeRate); } else { // redeeming all LPs - redeemedLPs_ = params_.lpConstraint; - unscaledRemovedAmount = Maths.rayToWad(Maths.rmul(redeemedLPs_, unscaledExchangeRate)); + redeemedLPs_ = params_.lpConstraint; + removedAmount_ = Maths.wmul(redeemedLPs_, exchangeRate); } // If clearing out the bucket deposit, ensure it's zeroed out if (redeemedLPs_ == params_.bucketLPs) { - unscaledRemovedAmount = unscaledDepositAvailable; + removedAmount_ = scaledDepositAvailable; } - // calculate the scaled amount removed from deposits - removedAmount_ = Maths.wmul(depositScale, unscaledRemovedAmount); - // calculate amount remaining - unscaledRemaining_ = unscaledDepositAvailable - unscaledRemovedAmount; - uint256 remaining = Maths.wmul(depositScale, unscaledRemaining_); - - // abandon dust amounts upon last withdrawal - if (remaining < params_.dustLimit && redeemedLPs_ == params_.bucketLPs) { - unscaledRemovedAmount = unscaledDepositAvailable; - unscaledRemaining_ = 0; - } + scaledRemaining_ = scaledDepositAvailable - removedAmount_; + uint256 unscaledRemovedAmount = Maths.min(unscaledDepositAvailable, Maths.wdiv(removedAmount_, depositScale)); Deposits.unscaledRemove(deposits_, params_.index, unscaledRemovedAmount); // update FenwickTree } diff --git a/src/libraries/helpers/PoolHelper.sol b/src/libraries/helpers/PoolHelper.sol index c8b279a4f..dcb7a79f7 100644 --- a/src/libraries/helpers/PoolHelper.sol +++ b/src/libraries/helpers/PoolHelper.sol @@ -191,7 +191,7 @@ import { Maths } from '../internal/Maths.sol'; // max collateral to lps uint256 rate = Buckets.getExchangeRate(bucketCollateral_, bucketLPs_, deposit_, bucketPrice_); - collateralAmount_ = Maths.rwdivw(Maths.rmul(lenderLPsBalance_, rate), bucketPrice_); + collateralAmount_ = Maths.wdiv(Maths.wmul(lenderLPsBalance_, rate), bucketPrice_); if (collateralAmount_ > bucketCollateral_) { // user is owed more collateral than is available in the bucket @@ -219,7 +219,7 @@ import { Maths } from '../internal/Maths.sol'; ) pure returns (uint256 quoteTokenAmount_) { uint256 rate = Buckets.getExchangeRate(bucketCollateral_, bucketLPs_, deposit_, bucketPrice_); - quoteTokenAmount_ = Maths.rayToWad(Maths.rmul(lenderLPsBalance_, rate)); + quoteTokenAmount_ = Maths.wmul(lenderLPsBalance_, rate); if (quoteTokenAmount_ > deposit_) quoteTokenAmount_ = deposit_; if (quoteTokenAmount_ > maxQuoteToken_) quoteTokenAmount_ = maxQuoteToken_; diff --git a/src/libraries/helpers/RevertsHelper.sol b/src/libraries/helpers/RevertsHelper.sol index 62e9473ec..fe2fc1bc7 100644 --- a/src/libraries/helpers/RevertsHelper.sol +++ b/src/libraries/helpers/RevertsHelper.sol @@ -10,7 +10,7 @@ import { PoolBalancesState } from '../../interfaces/pool/commons/IPoolState.sol'; -import { _minDebtAmount } from './PoolHelper.sol'; +import { _minDebtAmount, _priceAt } from './PoolHelper.sol'; import { Loans } from '../internal/Loans.sol'; import { Deposits } from '../internal/Deposits.sol'; @@ -20,7 +20,9 @@ import { Maths } from '../internal/Maths.sol'; error AuctionNotCleared(); error AmountLTMinDebt(); error DustAmountNotExceeded(); + error LimitIndexExceeded(); error RemoveDepositLockedByAuctionDebt(); + error TransactionExpired(); /** * @notice Called by LPB removal functions assess whether or not LPB is locked. @@ -58,6 +60,37 @@ import { Maths } from '../internal/Maths.sol'; } } + /** + * @notice Check if LUP is at or above index limit provided by borrower. + * @notice Prevents stale transactions and certain MEV manipulations. + * @param newLup_ New LUP as a result of the borrower action. + * @param limitIndex_ Limit price index provided by user creating the TX. + */ + function _revertIfLupDroppedBelowLimit( + uint256 newLup_, + uint256 limitIndex_ + ) pure { + if (newLup_ < _priceAt(limitIndex_)) revert LimitIndexExceeded(); + } + + /** + * @notice Check if expiration provided by user has met or exceeded current block height timestamp. + * @notice Prevents stale transactions interacting with the pool at potentially unfavorable prices. + * @param expiry_ Expiration provided by user when creating the TX. + */ + function _revertOnExpiry( + uint256 expiry_ + ) view { + if (block.timestamp >= expiry_) revert TransactionExpired(); + } + + /** + * @notice Called when borrower debt changes, ensuring minimum debt rules are honored. + * @param loans_ Loans heap, used to determine loan count. + * @param poolDebt_ Total pool debt, used to calculate average debt. + * @param borrowerDebt_ New debt for the borrower, assuming the current transaction succeeds. + * @param quoteDust_ Smallest amount of quote token when can be transferred, determined by token scale. + */ function _revertOnMinDebt( LoansState storage loans_, uint256 poolDebt_, @@ -65,11 +98,9 @@ import { Maths } from '../internal/Maths.sol'; uint256 quoteDust_ ) view { if (borrowerDebt_ != 0) { + if (borrowerDebt_ < quoteDust_) revert DustAmountNotExceeded(); uint256 loansCount = Loans.noOfLoans(loans_); - if (loansCount >= 10) { + if (loansCount >= 10) if (borrowerDebt_ < _minDebtAmount(poolDebt_, loansCount)) revert AmountLTMinDebt(); - } else { - if (borrowerDebt_ < quoteDust_) revert DustAmountNotExceeded(); - } } } diff --git a/src/libraries/internal/Buckets.sol b/src/libraries/internal/Buckets.sol index a192d9305..8075f9307 100644 --- a/src/libraries/internal/Buckets.sol +++ b/src/libraries/internal/Buckets.sol @@ -107,7 +107,7 @@ library Buckets { ) internal pure returns (uint256 lps_) { uint256 rate = getExchangeRate(bucketCollateral_, bucketLPs_, deposit_, bucketPrice_); - lps_ = (collateral_ * bucketPrice_ * 1e18 + rate / 2) / rate; + lps_ = Maths.wdiv(Maths.wmul(collateral_, bucketPrice_), rate); } /** @@ -126,8 +126,8 @@ library Buckets { uint256 quoteTokens_, uint256 bucketPrice_ ) internal pure returns (uint256) { - return Maths.rdiv( - Maths.wadToRay(quoteTokens_), + return Maths.wdiv( + quoteTokens_, getExchangeRate(bucketCollateral_, bucketLPs_, deposit_, bucketPrice_) ); } @@ -145,30 +145,7 @@ library Buckets { uint256 bucketDeposit_, uint256 bucketPrice_ ) internal pure returns (uint256) { - return bucketLPs_ == 0 - ? Maths.RAY - : (bucketDeposit_ * 1e18 + bucketPrice_ * bucketCollateral_) * 1e18 / bucketLPs_; - // 10^36 * 1e18 / 10^27 = 10^54 / 10^27 = 10^27 - } - - /** - * @notice Returns the unscaled exchange rate for a given bucket. - * @param bucketCollateral_ Amount of collateral in bucket. - * @param bucketLPs_ Amount of LPs in bucket. - * @param bucketUnscaledDeposit_ The amount of unscaled Fenwick tree amount in bucket. - * @param bucketScale_ Bucket scale factor - * @param bucketPrice_ Bucket's price. - */ - function getUnscaledExchangeRate( - uint256 bucketCollateral_, - uint256 bucketLPs_, - uint256 bucketUnscaledDeposit_, - uint256 bucketScale_, - uint256 bucketPrice_ - ) internal pure returns (uint256) { - return bucketLPs_ == 0 - ? Maths.RAY - : (bucketUnscaledDeposit_ + bucketPrice_ * bucketCollateral_ / bucketScale_ ) * 10**36 / bucketLPs_; - // 10^18 * 1e36 / 10^27 = 10^54 / 10^27 = 10^27 + return bucketLPs_ == 0 ? Maths.WAD : + Maths.wdiv(bucketDeposit_ + Maths.wmul(bucketPrice_, bucketCollateral_), bucketLPs_); } } diff --git a/src/libraries/internal/Loans.sol b/src/libraries/internal/Loans.sol index fefe20cc3..ff1bf1200 100644 --- a/src/libraries/internal/Loans.sol +++ b/src/libraries/internal/Loans.sol @@ -64,14 +64,14 @@ library Loans { * - remove: * - remove loan from loans array * - update borrower in address => borrower mapping - * @param loans_ Holds loan heap data. - * @param borrower_ Borrower struct with borrower details. - * @param borrowerAddress_ Borrower's address to update. - * @param borrowerAccruedDebt_ Borrower's current debt. - * @param poolRate_ Pool's current rate. - * @param lup_ Current LUP. - * @param inAuction_ Whether the loan is in auction or not. - * @param t0NpUpdate_ Whether the neutral price of borrower should be updated or not. + * @param loans_ Holds loan heap data. + * @param borrower_ Borrower struct with borrower details. + * @param borrowerAddress_ Borrower's address to update. + * @param poolDebt_ Pool's current debt. + * @param poolRate_ Pool's current rate. + * @param lup_ Current LUP. + * @param inAuction_ Whether the loan is in auction or not. + * @param t0NpUpdate_ Whether the neutral price of borrower should be updated or not. */ function update( LoansState storage loans_, @@ -79,7 +79,7 @@ library Loans { DepositsState storage deposits_, Borrower memory borrower_, address borrowerAddress_, - uint256 borrowerAccruedDebt_, + uint256 poolDebt_, uint256 poolRate_, uint256 lup_, bool inAuction_, @@ -111,7 +111,7 @@ library Loans { if (t0NpUpdate_) { if (t0ThresholdPrice != 0) { uint256 loansInPool = loans_.loans.length - 1 + auctions_.noOfAuctions; - uint256 curMomp = _priceAt(Deposits.findIndexOfSum(deposits_, Maths.wdiv(borrowerAccruedDebt_, loansInPool * 1e18))); + uint256 curMomp = _priceAt(Deposits.findIndexOfSum(deposits_, Maths.wdiv(poolDebt_, loansInPool * 1e18))); borrower_.t0Np = (1e18 + poolRate_) * curMomp * t0ThresholdPrice / lup_ / 1e18; } else { diff --git a/src/libraries/internal/Maths.sol b/src/libraries/internal/Maths.sol index 75d12c68f..c01f6e059 100644 --- a/src/libraries/internal/Maths.sol +++ b/src/libraries/internal/Maths.sol @@ -9,7 +9,6 @@ pragma solidity 0.8.14; library Maths { uint256 internal constant WAD = 1e18; - uint256 internal constant RAY = 10**27; function wmul(uint256 x, uint256 y) internal pure returns (uint256) { return (x * y + 1e18 / 2) / 1e18; @@ -35,30 +34,6 @@ library Maths { return (x * y + 10**27 / 2) / 10**27; } - function rdiv(uint256 x, uint256 y) internal pure returns (uint256) { - return (x * 10**27 + y / 2) / y; - } - - /** @notice Divides a WAD by a RAY and returns a RAY */ - function wrdivr(uint256 x, uint256 y) internal pure returns (uint256) { - return (x * 1e36 + y / 2) / y; - } - - /** @notice Divides a WAD by a WAD and returns a RAY */ - function wwdivr(uint256 x, uint256 y) internal pure returns (uint256) { - return (x * 1e27 + y / 2) / y; - } - - /** @notice Divides a RAY by another RAY and returns a WAD */ - function rrdivw(uint256 x, uint256 y) internal pure returns (uint256) { - return (x * 1e18 + y / 2) / y; - } - - /** @notice Divides a RAY by a WAD and returns a WAD */ - function rwdivw(uint256 x, uint256 y) internal pure returns (uint256) { - return (x * 1e9 + y / 2) / y; - } - function rpow(uint256 x, uint256 n) internal pure returns (uint256 z) { z = n % 2 != 0 ? x : 10**27; @@ -71,10 +46,6 @@ library Maths { } } - function wadToRay(uint256 x) internal pure returns (uint256) { - return x * 10**9; - } - function rayToWad(uint256 x) internal pure returns (uint256) { return (x + 10**9 / 2) / 10**9; } diff --git a/tests/INVARIANTS.md b/tests/INVARIANTS.md new file mode 100644 index 000000000..8799e40a3 --- /dev/null +++ b/tests/INVARIANTS.md @@ -0,0 +1,69 @@ +# Ajna Pool Invariants + +## Collateral +- #### ERC20: + - **CT1**: pool collateral token balance (`Collateral.balanceOf(pool)`) = sum of collateral balances across all borrowers (`Borrower.collateral`) + sum of claimable collateral across all buckets (`Bucket.collateral`) +- #### NFT: + - **CT2**: number of tokens owned by the pool (`Collateral.balanceOf(pool)`) * `1e18` = sum of collateral across all borrowers (`Borrower.collateral`) + sum of claimable collateral across all buckets (`Bucket.collateral`) + - **CT3**: number of tokens owned by the pool (`Collateral.balanceOf(pool)` = length of borrower array token ids (`ERC721Pool.borrowerTokenIds.length`) + length of buckets array token ids (`ERC721Pool.bucketTokenIds.length`) + - **CT4**: number of borrower token ids (`ERC721Pool.borrowerTokenIds.length`) * `1e18` <= borrower balance (`Borrower.collateral`) Note: can be lower in case when fractional collateral that is rebalanced / moved to buckets claimable token ids + - **CT5**: token ids in buckets array (`ERC721Pool.bucketTokenIds`) and in borrowers array (`ERC721Pool.borrowerTokenIds`) are owned by pool contract (`Collateral.ownerOf(tokenId)`) + - **CT6**: in case of subset pools: token ids in buckets array (`ERC721Pool.bucketTokenIds`) and in borrowers array (`ERC721Pool.borrowerTokenIds`) should have a mapping of `True` in allowed token ids mapping (`ERC721Pool.tokenIdsAllowed`) + +- **CT7**: total pledged collateral in pool (`PoolBalancesState.pledgedCollateral`) = sum of collateral balances across all borrowers (`Borrower.collateral`) + +## Quote Token +- **QT1**: pool quote token balance (`Quote.balanceOf(pool)`) >= liquidation bonds (`AuctionsState.totalBondEscrowed`) + pool deposit size (`Pool.depositSize()`) + reserve auction unclaimed amount (`reserveAuction.unclaimed`) - pool t0 debt (`PoolBalancesState.t0Debt`) +- **QT2**: pool t0 debt (`PoolBalancesState.t0Debt`) = sum of t0 debt across all borrowers (`Borrower.t0Debt`) + +## Auctions +- **A1**: total t0 debt auctioned (`PoolBalancesState.t0DebtInAuction`) = sum of debt across all auctioned borrowers (`Borrower.t0Debt` where borrower's `kickTime != 0`) +- **A2**: sum of bonds locked in auctions (`Liquidation.bondSize`) = sum of locked balances across all kickers (`Kicker.locked`) = total bond escrowed accumulator (`AuctionsState.totalBondEscrowed`) +- **A3**: number of borrowers with debt (`LoansState.borrowers.length` with `t0Debt != 0`) = number of loans (`LoansState.loans.length -1`) + number of auctioned borrowers (`AuctionsState.noOfAuctions`) +- **A4**: number of recorded auctions (`AuctionsState.noOfAuctions`) = length of auctioned borrowers (count of borrowers in `AuctionsState.liquidations` with `kickTime != 0`) +- **A5**: for each `Liquidation` recorded in liquidation mapping (`AuctionsState.liquidations`) the kicker address (`Liquidation.kicker`) has a locked balance (`Kicker.locked`) equal or greater than liquidation bond size (`Liquidation.bondSize`) +- **A6**: if a `Liquidation` is not taken then the take flag (`Liquidation.alreadyTaken`) should be `False`, if already taken then the take flag should be `True` + +## Loans +- **L1**: for each `Loan` in loans array (`LoansState.loans`) starting from index 1, the corresponding address (`Loan.borrower`) is not `0x`, the threshold price (`Loan.thresholdPrice`) is different than 0 and the id mapped in indices mapping (`LoansState.indices`) equals index of loan in loans array. +- **L2**: `Loan` in loans array (`LoansState.loans`) at index 0 has the corresponding address (`Loan.borrower`) equal with `0x` address and the threshold price (`Loan.thresholdPrice`) equal with 0 +- **L3**: Loans array (`LoansState.loans`) is a max-heap with respect to t0-threshold price: the t0TP of loan at index `i` is >= the t0-threshold price of the loans at index `2*i` and `2*i+1` + +## Buckets +- **B1**: sum of LPs of lenders in bucket (`Lender.lps`) = bucket LPs accumulator (`Bucket.lps`) +- **B2**: bucket LPs accumulator (`Bucket.lps`) = 0 if no deposit / collateral in bucket +- **B3**: if no collateral or deposit in bucket then the bucket exchange rate is `1e27` +- **B4**: bankrupt bucket LPs accumulator = 0; lender LPs for deposits before bankruptcy time = 0 +- **B5**: when adding quote tokens: lender deposit time (`Lender.depositTime`) = timestamp of block when deposit happened (`block.timestamp`) + +## Interest +- **I1**: interest rate (`InterestState.interestRate`) cannot be updated more than once in a 12 hours period of time (`InterestState.interestRateUpdate`) +- **I2**: reserve interest (`ReserveAuctionState.totalInterestEarned`) accrues only once per block (`block.timestamp - InflatorState.inflatorUpdate != 0`) and only if there's debt in the pool (`PoolBalancesState.t0Debt != 0`) +- **I3**: pool inflator (`InflatorState.inflator`) cannot be updated more than once per block (`block.timestamp - InflatorState.inflatorUpdate != 0`) and equals `1e18` if there's no debt in the pool (`PoolBalancesState.t0Debt != 0`) + +## Fenwick tree +- **F1**: Value represented at index `i` (`Deposits.valueAt(i)`) is equal to the accumulation of scaled values incremented or decremented from index `i` +- **F2**: For any index `i`, the prefix sum up to and including `i` is the sum of values stored in indices `j<=i` +- **F3**: For any index `i < MAX_FENWICK_INDEX`, `findIndexOfSum(prefixSum(i)) > i` +- **F4**: For any index `i`, there is zero deposit above `i` and below `findIndexOfSum(prefixSum(i))`: `prefixSum(findIndexOfSum(prefixSum(i))-1 == prefixSum(i)' + +## Exchange rate invariants ## +- **R1**: Exchange rates are unchanged by pledging collateral +- **R2**: Exchange rates are unchanged by removing collateral +- **R3**: Exchange rates are unchanged by depositing quote token into a bucket +- **R4**: Exchange rates are unchanged by withdrawing deposit (quote token) from a bucket +- **R5**: Exchange rates are unchanged by adding collateral token into a bucket +- **R6**: Exchange rates are unchanged by removing collateral token from a bucket +- **R7**: Exchange rates are unchanged under depositTakes +- **R8**: Exchange rates are unchanged under arbTakes + +## Reserves ## +- **RE1**: Reserves are unchanged by pledging collateral +- **RE2**: Reserves are unchanged by removing collateral +- **RE3**: Reserves are unchanged by depositing quote token into a bucket +- **RE4**: Reserves are unchanged by withdrawing deposit (quote token) from a bucket after the penalty period hes expired +- **RE5**: Reserves are unchanged by adding collateral token into a bucket +- **RE6**: Reserves are unchanged by removing collateral token from a bucket +- **RE7**: Reserves increase by 7% of the loan quantity upon the first take (including depositTake or arbTake) +- **RE8**: Reserves are unchanged under takes/depositTakes/arbTakes after the first take +- **RE9**: Reserves increase by .25% of the debt when a loan is kicked diff --git a/tests/README.md b/tests/README.md index 2fc707430..7cccef612 100644 --- a/tests/README.md +++ b/tests/README.md @@ -12,6 +12,10 @@ make test-with-gas-report ```bash make test-load ``` +- run invariant tests: +```bash +make test-invariant +``` - generate code coverage report: ```bash make coverage diff --git a/tests/brownie/test_scaled_pool.py b/tests/brownie/test_scaled_pool.py index de812d77e..c926480d1 100644 --- a/tests/brownie/test_scaled_pool.py +++ b/tests/brownie/test_scaled_pool.py @@ -7,13 +7,14 @@ def test_quote_deposit_move_remove_scaled( lenders, scaled_pool, + chain, capsys, test_utils ): with test_utils.GasWatcher(["addQuoteToken", "moveQuoteToken", "removeQuoteToken"]): add_txes = [] for i in range(2530, 2550): - tx = scaled_pool.addQuoteToken(100 * 10**18, i, {"from": lenders[0]}) + tx = scaled_pool.addQuoteToken(100 * 10**18, i, chain.time() + 30, {"from": lenders[0]}) add_txes.append(tx) with capsys.disabled(): print("\n==================================") @@ -24,7 +25,7 @@ def test_quote_deposit_move_remove_scaled( move_txes = [] for i in range(2530, 2550): - tx = scaled_pool.moveQuoteToken(100 * 10**18, i, i + 30, {"from": lenders[0]}) + tx = scaled_pool.moveQuoteToken(100 * 10**18, i, i + 30, chain.time() + 30, {"from": lenders[0]}) move_txes.append(tx) with capsys.disabled(): print("\n==================================") @@ -49,14 +50,16 @@ def test_borrow_repay_scaled( lenders, borrowers, scaled_pool, + chain, capsys, test_utils ): with test_utils.GasWatcher(["addQuoteToken", "drawDebt", "repayDebt"]): - scaled_pool.addQuoteToken(100 * 10**18, 2550, {"from": lenders[0]}) - scaled_pool.addQuoteToken(100 * 10**18, 2560, {"from": lenders[0]}) - scaled_pool.addQuoteToken(100 * 10**18, 2570, {"from": lenders[0]}) + expiry = chain.time() + 30 + scaled_pool.addQuoteToken(100 * 10**18, 2550, expiry, {"from": lenders[0]}) + scaled_pool.addQuoteToken(100 * 10**18, 2560, expiry, {"from": lenders[0]}) + scaled_pool.addQuoteToken(100 * 10**18, 2570, expiry, {"from": lenders[0]}) col_txes = [] for i in range(10): @@ -85,11 +88,11 @@ def test_borrow_repay_scaled( print(f"Transaction: {i} | {test_utils.get_usage(txes[i].gas_used)}") repay_txes = [] - tx = scaled_pool.repayDebt(borrowers[0], 110 * 10**18, 0, {"from": borrowers[0]}) + tx = scaled_pool.repayDebt(borrowers[0], 110 * 10**18, 0, borrowers[0], 7388, {"from": borrowers[0]}) repay_txes.append(tx) - tx = scaled_pool.repayDebt(borrowers[0], 110 * 10**18, 0, {"from": borrowers[0]}) + tx = scaled_pool.repayDebt(borrowers[0], 110 * 10**18, 0, borrowers[0], 7388, {"from": borrowers[0]}) repay_txes.append(tx) - tx = scaled_pool.repayDebt(borrowers[0], 50 * 10**18, 0, {"from": borrowers[0]}) + tx = scaled_pool.repayDebt(borrowers[0], 50 * 10**18, 0, borrowers[0], 7388, {"from": borrowers[0]}) repay_txes.append(tx) with capsys.disabled(): print("\n==================================") diff --git a/tests/brownie/test_stable_volatile.py b/tests/brownie/test_stable_volatile.py index e121ba1d4..49f6c1762 100644 --- a/tests/brownie/test_stable_volatile.py +++ b/tests/brownie/test_stable_volatile.py @@ -76,7 +76,7 @@ def borrowers(ajna_protocol, scaled_pool): def pool_helper(ajna_protocol, scaled_pool, lenders, borrowers, test_utils, chain): pool_helper = PoolHelper(ajna_protocol, scaled_pool) # Adds liquidity to an empty pool and draws debt up to a target utilization - add_initial_liquidity(lenders, pool_helper) + add_initial_liquidity(lenders, pool_helper, chain) draw_initial_debt(borrowers, pool_helper, test_utils, chain, target_utilization=GOAL_UTILIZATION) global last_triggered last_triggered = dict.fromkeys(range(0, max(NUM_LENDERS, NUM_BORROWERS)), 0) @@ -84,7 +84,7 @@ def pool_helper(ajna_protocol, scaled_pool, lenders, borrowers, test_utils, chai return pool_helper -def add_initial_liquidity(lenders, pool_helper): +def add_initial_liquidity(lenders, pool_helper, chain): # Lenders 0-9 will be "new to the pool" upon actual testing # TODO: determine this non-arbitrarily deposit_amount = MIN_PARTICIPATION * 10**18 @@ -97,7 +97,7 @@ def add_initial_liquidity(lenders, pool_helper): price_index = price_position + MAX_BUCKET log(f" lender {i} depositing {deposit_amount/1e18} into bucket {price_index} " f"({pool_helper.indexToPrice(price_index) / 1e18:.1f})") - pool_helper.pool.addQuoteToken(deposit_amount, price_index, {"from": lenders[i]}) + pool_helper.pool.addQuoteToken(deposit_amount, price_index, chain.time() + 30, {"from": lenders[i]}) def draw_initial_debt(borrowers, pool_helper, test_utils, chain, target_utilization): @@ -249,7 +249,7 @@ def draw_and_bid(lenders, borrowers, start_from, pool_helper, chain, test_utils, # Add or remove liquidity if user_index < NUM_LENDERS: if random.choice([True, False]): - price = add_quote_token(lenders[user_index], user_index, pool_helper) + price = add_quote_token(lenders[user_index], user_index, pool_helper, chain) if price: buckets_deposited[user_index].add(price) else: @@ -294,7 +294,7 @@ def draw_debt(borrower, borrower_index, pool_helper, test_utils, collateralizati tx = pledge_and_borrow(pool_helper, borrower, borrower_index, collateral_to_deposit, borrow_amount, test_utils) -def add_quote_token(lender, lender_index, pool_helper): +def add_quote_token(lender, lender_index, pool_helper, chain): dai = pool_helper.quoteToken() index_offset = ((lender_index % 6) - 2) * 2 lup_index = pool_helper.lupIndex() @@ -307,7 +307,7 @@ def add_quote_token(lender, lender_index, pool_helper): return None log(f" lender {lender_index:>4} adding {quantity / 10**18:.1f} liquidity at {deposit_price / 10**18:.1f}") - tx = pool_helper.pool.addQuoteToken(quantity, deposit_index, {"from": lender}) + tx = pool_helper.pool.addQuoteToken(quantity, deposit_index, chain.time() + 15, {"from": lender}) return deposit_price @@ -364,7 +364,7 @@ def repay_debt(borrower, borrower_index, pool_helper, test_utils): f"is withdrawing {collateral_deposited/1e18:.1f} collateral") # assert collateral_to_withdraw > 0 repay_amount = int(repay_amount * 1.01) - tx = pool_helper.pool.repayDebt(borrower, repay_amount, collateral_to_withdraw, {"from": borrower}) + tx = pool_helper.pool.repayDebt(borrower, repay_amount, collateral_to_withdraw, borrower, 7388, {"from": borrower}) elif debt == 0: log(f" borrower {borrower_index:>4} has no debt to repay") else: diff --git a/tests/forge/ERC20Pool/ERC20DSTestPlus.sol b/tests/forge/ERC20Pool/ERC20DSTestPlus.sol index 38badd274..0cc630f02 100644 --- a/tests/forge/ERC20Pool/ERC20DSTestPlus.sol +++ b/tests/forge/ERC20Pool/ERC20DSTestPlus.sol @@ -75,8 +75,9 @@ abstract contract ERC20DSTestPlus is DSTestPlus, IERC20PoolEvents { (, uint256 bucketQuote, uint256 bucketCollateral, , ,) = _poolUtils.bucketInfo(address(_pool), bucketIndex); (uint256 lenderLpBalance, ) = _pool.lenderInfo(bucketIndex, lender); - // redeem LP for quote token if available uint256 lpRedeemed; + + // redeem LP for quote token if available if(lenderLpBalance != 0 && bucketQuote != 0) { (, lpRedeemed) = _pool.removeQuoteToken(type(uint256).max, bucketIndex); lenderLpBalance -= lpRedeemed; @@ -88,8 +89,6 @@ abstract contract ERC20DSTestPlus is DSTestPlus, IERC20PoolEvents { lenderLpBalance -= lpRedeemed; } - // confirm the redemption amount returned by removal methods is correct - assertEq(lenderLpBalance, 0); // confirm the user actually has 0 LPB in the bucket (lenderLpBalance, ) = _pool.lenderInfo(bucketIndex, lender); assertEq(lenderLpBalance, 0); @@ -195,7 +194,7 @@ abstract contract ERC20DSTestPlus is DSTestPlus, IERC20PoolEvents { lendersDepositedIndex[from].add(index); bucketsUsed.add(index); - return ERC20Pool(address(_pool)).addCollateral(amount, index); + return ERC20Pool(address(_pool)).addCollateral(amount, index, type(uint256).max); } function _addCollateralWithoutCheckingLP( @@ -218,7 +217,7 @@ abstract contract ERC20DSTestPlus is DSTestPlus, IERC20PoolEvents { lendersDepositedIndex[from].add(index); bucketsUsed.add(index); - return ERC20Pool(address(_pool)).addCollateral(amount, index); + return ERC20Pool(address(_pool)).addCollateral(amount, index, type(uint256).max); } function _borrow( @@ -366,7 +365,7 @@ abstract contract ERC20DSTestPlus is DSTestPlus, IERC20PoolEvents { vm.expectEmit(true, true, false, true); emit RepayDebt(borrower, repaid, 0, newLup); _assertQuoteTokenTransferEvent(from, address(_pool), repaid); - ERC20Pool(address(_pool)).repayDebt(borrower, amount, 0); + ERC20Pool(address(_pool)).repayDebt(borrower, amount, 0, borrower, MAX_FENWICK_INDEX); } function _repayDebt( @@ -394,7 +393,23 @@ abstract contract ERC20DSTestPlus is DSTestPlus, IERC20PoolEvents { _assertCollateralTokenTransferEvent(address(_pool), from, collateralToPull); } - ERC20Pool(address(_pool)).repayDebt(borrower, amountToRepay, collateralToPull); + ERC20Pool(address(_pool)).repayDebt(borrower, amountToRepay, collateralToPull, borrower, MAX_FENWICK_INDEX); + } + + function _repayDebtAndPullToRecipient( + address from, + address borrower, + address recipient, + uint256 amountToRepay, + uint256 amountRepaid, + uint256 collateralToPull, + uint256 newLup + ) internal { + changePrank(from); + vm.expectEmit(true, true, false, true); + emit RepayDebt(borrower, amountRepaid, collateralToPull, newLup); + _assertCollateralTokenTransferEvent(address(_pool), recipient, collateralToPull); + ERC20Pool(address(_pool)).repayDebt(borrower, amountToRepay, collateralToPull, recipient, MAX_FENWICK_INDEX); } function _repayDebtNoLupCheck( @@ -407,7 +422,7 @@ abstract contract ERC20DSTestPlus is DSTestPlus, IERC20PoolEvents { _repayDebt(from, borrower, amountToRepay, amountRepaid, collateralToPull, 0); } - function _transferLpTokens( + function _transferLPs( address operator, address from, address to, @@ -416,7 +431,7 @@ abstract contract ERC20DSTestPlus is DSTestPlus, IERC20PoolEvents { ) internal { changePrank(operator); vm.expectEmit(true, true, true, true); - emit TransferLPTokens(from, to, indexes, lpBalance); + emit TransferLPs(from, to, indexes, lpBalance); _pool.transferLPs(from, to, indexes); for(uint256 i = 0; i < indexes.length ;i++ ){ @@ -439,7 +454,7 @@ abstract contract ERC20DSTestPlus is DSTestPlus, IERC20PoolEvents { ) internal { changePrank(from); vm.expectRevert(abi.encodeWithSignature('BucketBankruptcyBlock()')); - ERC20Pool(address(_pool)).addCollateral(amount, index); + ERC20Pool(address(_pool)).addCollateral(amount, index, type(uint256).max); } function _assertAddCollateralAtIndex0Revert( @@ -448,7 +463,7 @@ abstract contract ERC20DSTestPlus is DSTestPlus, IERC20PoolEvents { ) internal { changePrank(from); vm.expectRevert(IPoolErrors.InvalidIndex.selector); - ERC20Pool(address(_pool)).addCollateral(amount, 0); + ERC20Pool(address(_pool)).addCollateral(amount, 0, type(uint256).max); } function _assertAddCollateralDustRevert( @@ -458,7 +473,18 @@ abstract contract ERC20DSTestPlus is DSTestPlus, IERC20PoolEvents { ) internal { changePrank(from); vm.expectRevert(IPoolErrors.DustAmountNotExceeded.selector); - ERC20Pool(address(_pool)).addCollateral(amount, index); + ERC20Pool(address(_pool)).addCollateral(amount, index, type(uint256).max); + } + + function _assertAddCollateralExpiredRevert( + address from, + uint256 amount, + uint256 index, + uint256 expiry + ) internal { + changePrank(from); + vm.expectRevert(IPoolErrors.TransactionExpired.selector); + ERC20Pool(address(_pool)).addCollateral(amount, index, expiry); } function _assertDeployWith0xAddressRevert( @@ -497,7 +523,17 @@ abstract contract ERC20DSTestPlus is DSTestPlus, IERC20PoolEvents { ) internal { changePrank(from); vm.expectRevert(IPoolErrors.InsufficientCollateral.selector); - ERC20Pool(address(_pool)).repayDebt(from, 0, amount); + ERC20Pool(address(_pool)).repayDebt(from, 0, amount, from, MAX_FENWICK_INDEX); + } + + function _assertPullLimitIndexRevert( + address from, + uint256 amount, + uint256 indexLimit + ) internal { + changePrank(from); + vm.expectRevert(IPoolErrors.LimitIndexExceeded.selector); + ERC20Pool(address(_pool)).repayDebt(from, 0, amount, from, indexLimit); } function _assertRepayNoDebtRevert( @@ -507,7 +543,7 @@ abstract contract ERC20DSTestPlus is DSTestPlus, IERC20PoolEvents { ) internal { changePrank(from); vm.expectRevert(IPoolErrors.NoDebt.selector); - ERC20Pool(address(_pool)).repayDebt(borrower, amount, 0); + ERC20Pool(address(_pool)).repayDebt(borrower, amount, 0, borrower, MAX_FENWICK_INDEX); } function _assertPullBorrowerNotSenderRevert( @@ -517,7 +553,7 @@ abstract contract ERC20DSTestPlus is DSTestPlus, IERC20PoolEvents { ) internal { changePrank(from); vm.expectRevert(IPoolErrors.BorrowerNotSender.selector); - ERC20Pool(address(_pool)).repayDebt(borrower, 0, amount); + ERC20Pool(address(_pool)).repayDebt(borrower, 0, amount, borrower, MAX_FENWICK_INDEX); } function _assertRepayMinDebtRevert( @@ -527,7 +563,7 @@ abstract contract ERC20DSTestPlus is DSTestPlus, IERC20PoolEvents { ) internal { changePrank(from); vm.expectRevert(IPoolErrors.AmountLTMinDebt.selector); - ERC20Pool(address(_pool)).repayDebt(borrower, amount, 0); + ERC20Pool(address(_pool)).repayDebt(borrower, amount, 0, borrower, MAX_FENWICK_INDEX); } function _assertRemoveAllCollateralNoClaimRevert( @@ -580,6 +616,17 @@ abstract contract ERC20DSTestPlus is DSTestPlus, IERC20PoolEvents { _pool.transferLPs(from, to, indexes); } + function _assertTransferToSameOwnerRevert( + address operator, + address from, + address to, + uint256[] memory indexes + ) internal { + changePrank(operator); + vm.expectRevert(IPoolErrors.TransferToSameOwner.selector); + _pool.transferLPs(from, to, indexes); + } + function _assertDepositLockedByAuctionDebtRevert( address operator, uint256 amount, @@ -596,7 +643,7 @@ abstract contract ERC20DSTestPlus is DSTestPlus, IERC20PoolEvents { uint256 indexLimit ) internal override { changePrank(from); - vm.expectRevert(IPoolErrors.BorrowerUnderCollateralized.selector); + vm.expectRevert(IPoolErrors.AuctionActive.selector); ERC20Pool(address(_pool)).drawDebt(from, amount, indexLimit, 0); } @@ -617,7 +664,7 @@ abstract contract ERC20DSTestPlus is DSTestPlus, IERC20PoolEvents { uint256 indexLimit ) internal override { changePrank(from); - vm.expectRevert(IPoolErrors.LimitIndexReached.selector); + vm.expectRevert(IPoolErrors.LimitIndexExceeded.selector); ERC20Pool(address(_pool)).drawDebt(from, amount, indexLimit, 0); } diff --git a/tests/forge/ERC20Pool/ERC20FlashloanCollateral.t.sol b/tests/forge/ERC20Pool/ERC20FlashloanCollateral.t.sol deleted file mode 100644 index 25d971830..000000000 --- a/tests/forge/ERC20Pool/ERC20FlashloanCollateral.t.sol +++ /dev/null @@ -1,141 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.14; - -import { ERC20HelperContract } from './ERC20DSTestPlus.sol'; -import { FlashloanBorrower, SomeDefiStrategy } from '../utils/FlashloanBorrower.sol'; - -import 'src/libraries/helpers/PoolHelper.sol'; -import 'src/ERC20Pool.sol'; - -import { IPoolErrors } from 'src/interfaces/pool/IPool.sol'; - -contract ERC20PoolFlashloanTest is ERC20HelperContract { - address internal _borrower; - address internal _lender; - uint internal _bucketId; - uint internal _bucketPrice; - - function setUp() external { - _lender = makeAddr("lender"); - _borrower = makeAddr("borrower"); - - _mintQuoteAndApproveTokens(_lender, 100_000 * 1e18); - _mintQuoteAndApproveTokens(_borrower, 5_000 * 1e18); - _mintCollateralAndApproveTokens(_borrower, 100 * 1e18); - - // lender adds liquidity - _bucketPrice = 502.433988063349232760 * 1e18; - _bucketId = _indexOf(_bucketPrice); - assertEq(_bucketId, 2909); - _addInitialLiquidity( - { - from: _lender, - amount: 100_000 * 1e18, - index: _bucketId - } - ); - - // borrower draws debt - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - amount: 100 * 1e18 - } - ); - _borrow( - { - from: _borrower, - amount: 25_000 * 1e18, - indexLimit: _bucketId, - newLup: _bucketPrice - } - ); - (uint256 poolDebt,,) = _pool.debtInfo(); - assertEq(poolDebt, 25_024.038461538461550000 * 1e18); - } - - function testCollateralFlashloan() external tearDown { - skip(1 days); - uint256 loanAmount = 100 * 1e18; - assertEq(_pool.maxFlashLoan(address(_collateral)), loanAmount); - - // Create an example defi strategy - SomeDefiStrategy strategy = new SomeDefiStrategy(_collateral); - deal(address(_collateral), address(strategy), 10 * 1e18); - - // Create a flashloan borrower contract which interacts with the strategy - bytes memory strategyCalldata = abi.encodeWithSignature("makeMoney(uint256)", loanAmount); - FlashloanBorrower flasher = new FlashloanBorrower(address(strategy), strategyCalldata); - - // Run the token approvals - changePrank(address(flasher)); - _collateral.approve(address(_pool), loanAmount); - _collateral.approve(address(strategy), loanAmount); - - // Use a flashloan to interact with the strategy - assertEq(_collateral.balanceOf(address(flasher)), 0); - assertTrue(!flasher.callbackInvoked()); - _pool.flashLoan(flasher, address(_collateral), loanAmount, new bytes(0)); - assertTrue(flasher.callbackInvoked()); - assertEq(_collateral.balanceOf(address(flasher)), 3.5 * 1e18); - } - - function testFlashloanFee() external tearDown { - uint256 loanAmount = 100 * 1e18; - - // Ensure there is no fee for quote token - uint256 fee = _pool.flashFee(address(_quote), loanAmount); - assertEq(fee, 0); - - // Ensure there is no fee for collateral - fee = _pool.flashFee(address(_collateral), loanAmount); - assertEq(fee, 0); - - // Ensure fee reverts for a random address which isn't a token - _assertFlashloanFeeRevertsForToken(makeAddr("nobody"), loanAmount); - } - - function testMaxFlashloan() external tearDown { - assertEq(_pool.maxFlashLoan(_pool.quoteTokenAddress()), 75_000 * 1e18); - assertEq(_pool.maxFlashLoan(_pool.collateralAddress()), 100 * 1e18); - assertEq(_pool.maxFlashLoan(makeAddr("nobody")), 0); - } - - function testCannotFlashloanMoreCollateralThanAvailable() external tearDown { - FlashloanBorrower flasher = new FlashloanBorrower(address(0), new bytes(0)); - - // Cannot flashloan less than pool size but more than available quote token - _assertFlashloanTooLargeRevert(flasher, _pool.quoteTokenAddress(), 90_000 * 1e18); - - // Cannot flashloan more collateral than pledged - _assertFlashloanTooLargeRevert(flasher, _pool.collateralAddress(), 150 * 1e18); - } - - function testCannotFlashloanNonToken() external tearDown { - FlashloanBorrower flasher = new FlashloanBorrower(address(0), new bytes(0)); - - // Cannot flashloan a random address which isn't a token - _assertFlashloanUnavailableForToken(flasher, makeAddr("nobody"), 1); - } - - function testCallbackFailure() external tearDown { - uint256 loanAmount = 100 * 1e18; - - // Create an example defi strategy - SomeDefiStrategy strategy = new SomeDefiStrategy(_collateral); - - // Create a flashloan borrower contract which invokes a non-existant method on the strategy - bytes memory strategyCalldata = abi.encodeWithSignature("missing()"); - FlashloanBorrower flasher = new FlashloanBorrower(address(strategy), strategyCalldata); - - // Run approvals - changePrank(address(flasher)); - _quote.approve(address(_pool), loanAmount); - - // Make a failed attempt to interact with the strategy - vm.expectRevert(IPoolErrors.FlashloanCallbackFailed.selector); - _pool.flashLoan(flasher, address(_collateral), loanAmount, new bytes(0)); - assertFalse(flasher.callbackInvoked()); - } -} \ No newline at end of file diff --git a/tests/forge/ERC20Pool/ERC20PoolBorrow.t.sol b/tests/forge/ERC20Pool/ERC20PoolBorrow.t.sol index b30e6ea11..cfa46f2bc 100644 --- a/tests/forge/ERC20Pool/ERC20PoolBorrow.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolBorrow.t.sol @@ -33,52 +33,42 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { _mintQuoteAndApproveTokens(_lender, 200_000 * 1e18); _mintQuoteAndApproveTokens(_lender1, 200_000 * 1e18); - // lender deposits 10000 DAI in 5 buckets each - _addLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: highest, - lpAward: 10_000 * 1e27, - newLup: MAX_PRICE - } - ); - _addLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: high, - lpAward: 10_000 * 1e27, - newLup: MAX_PRICE - } - ); - _addLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: med, - lpAward: 10_000 * 1e27, - newLup: MAX_PRICE - } - ); - _addLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: low, - lpAward: 10_000 * 1e27, - newLup: MAX_PRICE - } - ); - _addLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: lowest, - lpAward: 10_000 * 1e27, - newLup: MAX_PRICE - } - ); + // lender deposits 10000 quote in 5 buckets each + _addLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: highest, + lpAward: 10_000 * 1e18, + newLup: MAX_PRICE + }); + _addLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: high, + lpAward: 10_000 * 1e18, + newLup: MAX_PRICE + }); + _addLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: med, + lpAward: 10_000 * 1e18, + newLup: MAX_PRICE + }); + _addLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: low, + lpAward: 10_000 * 1e18, + newLup: MAX_PRICE + }); + _addLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: lowest, + lpAward: 10_000 * 1e18, + newLup: MAX_PRICE + }); _assertPool( PoolParams({ @@ -135,103 +125,81 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { assertEq(_quote.balanceOf(address(_pool)), 29_000 * 1e18); assertEq(_quote.balanceOf(_lender), 150_000 * 1e18); - _assertLenderLpBalance( - { - lender: _lender, - index: highest, - lpBalance: 10_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: high, - lpBalance: 10_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: med, - lpBalance: 10_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: low, - lpBalance: 10_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: lowest, - lpBalance: 10_000 * 1e27, - depositTime: _startTime - } - ); + _assertLenderLpBalance({ + lender: _lender, + index: highest, + lpBalance: 10_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: _lender, + index: high, + lpBalance: 10_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: _lender, + index: med, + lpBalance: 10_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: _lender, + index: low, + lpBalance: 10_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: _lender, + index: lowest, + lpBalance: 10_000 * 1e18, + depositTime: _startTime + }); // check buckets - _assertBucket( - { - index: highest, - lpBalance: 10_000 * 1e27, - collateral: 0, - deposit: 10_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); - _assertBucket( - { - index: high, - lpBalance: 10_000 * 1e27, - collateral: 0, - deposit: 10_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); - _assertBucket( - { - index: med, - lpBalance: 10_000 * 1e27, - collateral: 0, - deposit: 10_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); - _assertBucket( - { - index: low, - lpBalance: 10_000 * 1e27, - collateral: 0, - deposit: 10_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); - _assertBucket( - { - index: lowest, - lpBalance: 10_000 * 1e27, - collateral: 0, - deposit: 10_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); + _assertBucket({ + index: highest, + lpBalance: 10_000 * 1e18, + collateral: 0, + deposit: 10_000 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertBucket({ + index: high, + lpBalance: 10_000 * 1e18, + collateral: 0, + deposit: 10_000 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertBucket({ + index: med, + lpBalance: 10_000 * 1e18, + collateral: 0, + deposit: 10_000 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertBucket({ + index: low, + lpBalance: 10_000 * 1e18, + collateral: 0, + deposit: 10_000 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertBucket({ + index: lowest, + lpBalance: 10_000 * 1e18, + collateral: 0, + deposit: 10_000 * 1e18, + exchangeRate: 1 * 1e18 + }); - // borrow 19_000 DAI - _borrow( - { - from: _borrower, - amount: 19_000 * 1e18, - indexLimit: 3_500, - newLup: 2_951.419442869698640451 * 1e18 - } - ); + // borrow 19_000 quote + _borrow({ + from: _borrower, + amount: 19_000 * 1e18, + indexLimit: 3_500, + newLup: 2_951.419442869698640451 * 1e18 + }); _assertPool( PoolParams({ @@ -250,6 +218,7 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { interestRateUpdate: _startTime }) ); + // check balances assertEq(_quote.balanceOf(address(_pool)), 10_000 * 1e18); assertEq(_quote.balanceOf(_lender), 150_000 * 1e18); @@ -319,15 +288,13 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { assertEq(_quote.balanceOf(address(_pool)), 50_038.461538461538480000 * 1e18); assertEq(_quote.balanceOf(_lender), 150_000 * 1e18); - // borrow 8_000 DAI - _borrow( - { - from: _borrower, - amount: 8_000 * 1e18, - indexLimit: 3_500, - newLup: 3_010.892022197881557845 * 1e18 - } - ); + // borrow 8_000 quote + _borrow({ + from: _borrower, + amount: 8_000 * 1e18, + indexLimit: 3_500, + newLup: 3_010.892022197881557845 * 1e18 + }); _assertPool( PoolParams({ @@ -350,7 +317,9 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { function testPoolBorrowerInterestAccumulation() external tearDown { (uint256 liquidityAdded, , , , ) = _poolUtils.poolLoansInfo(address(_pool)); + skip(10 days); + _drawDebt({ from: _borrower, borrower: _borrower, @@ -361,6 +330,7 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { }); uint256 expectedDebt = 21_051.890446235135648008 * 1e18; + _assertPool( PoolParams({ htp: 420.403846153846154040 * 1e18, @@ -378,26 +348,24 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { interestRateUpdate: _startTime + 10 days }) ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: expectedDebt, - borrowerCollateral: 50 * 1e18, - borrowert0Np: 441.424038461538461742 * 1e18, - borrowerCollateralization: 7.080141877038845214 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: expectedDebt, + borrowerCollateral: 50 * 1e18, + borrowert0Np: 441.424038461538461742 * 1e18, + borrowerCollateralization: 7.080141877038845214 * 1e18 + }); skip(10 days); - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - amount: 10 * 1e18 - } - ); + + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + amount: 10 * 1e18 + }); expectedDebt = 21_083.636385101213387311 * 1e18; + _assertPool( PoolParams({ htp: 352.454532537342231182 * 1e18, @@ -415,18 +383,17 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { interestRateUpdate: _startTime + 20 days }) ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: expectedDebt, - borrowerCollateral: 60 * 1e18, - borrowert0Np: 441.424038461538461742 * 1e18, - borrowerCollateralization: 8.483377444958217435 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: expectedDebt, + borrowerCollateral: 60 * 1e18, + borrowert0Np: 441.424038461538461742 * 1e18, + borrowerCollateralization: 8.483377444958217435 * 1e18 + }); _assertLenderInterest(liquidityAdded, 55.509493137959600000 * 1e18); skip(10 days); + _repayDebtNoLupCheck({ from: _borrower, borrower: _borrower, @@ -436,6 +403,7 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { }); expectedDebt = 21_118.612213260575680078 * 1e18; + _assertPool( PoolParams({ htp: 424.349858731660857846 * 1e18, @@ -453,28 +421,26 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { interestRateUpdate: _startTime + 30 days }) ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: expectedDebt, - borrowerCollateral: 50 * 1e18, - borrowert0Np: 445.838278846153846359 * 1e18, - borrowerCollateralization: 7.057773002983275247 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: expectedDebt, + borrowerCollateral: 50 * 1e18, + borrowert0Np: 445.838278846153846359 * 1e18, + borrowerCollateralization: 7.057773002983275247 * 1e18 + }); _assertLenderInterest(liquidityAdded, 86.113113158840750000 * 1e18); skip(10 days); - _borrowZeroAmount( - { - from: _borrower, - amount: 0, - indexLimit: 3_000, - newLup: 2_981.007422784467321543 * 1e18 - } - ); + + _borrowZeroAmount({ + from: _borrower, + amount: 0, + indexLimit: 3_000, + newLup: 2_981.007422784467321543 * 1e18 + }); expectedDebt = 21_157.152643010853304038 * 1e18; + _assertPool( PoolParams({ htp: 425.900107294311861922 * 1e18, @@ -492,15 +458,13 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { interestRateUpdate: _startTime + 40 days }) ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: expectedDebt, - borrowerCollateral: 50 * 1e18, - borrowert0Np: 448.381722115384615591 * 1e18, - borrowerCollateralization: 7.044916376706357984 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: expectedDebt, + borrowerCollateral: 50 * 1e18, + borrowert0Np: 448.381722115384615591 * 1e18, + borrowerCollateralization: 7.044916376706357984 * 1e18 + }); _assertLenderInterest(liquidityAdded, 119.836959946754650000 * 1e18); skip(10 days); @@ -509,6 +473,7 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { IERC20Pool(address(_pool)).drawDebt(_borrower, 0, 0, 0); expectedDebt = 21_199.628356897284442294 * 1e18; + _assertPool( PoolParams({ htp: 427.611922756860156608 * 1e18, @@ -526,19 +491,19 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { interestRateUpdate: _startTime + 50 days }) ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: expectedDebt, - borrowerCollateral: 50 * 1e18, - borrowert0Np: 448.381722115384615591 * 1e18, - borrowerCollateralization: 7.030801136225104190 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: expectedDebt, + borrowerCollateral: 50 * 1e18, + borrowert0Np: 448.381722115384615591 * 1e18, + borrowerCollateralization: 7.030801136225104190 * 1e18 + }); _assertLenderInterest(liquidityAdded, 157.005764521268350000 * 1e18); skip(10 days); + expectedDebt = 21_246.450141935843866714 * 1e18; + _assertPool( PoolParams({ htp: 427.611922756860156608 * 1e18, @@ -556,15 +521,13 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { interestRateUpdate: _startTime + 50 days }) ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: expectedDebt, - borrowerCollateral: 50 * 1e18, - borrowert0Np: 448.381722115384615591 * 1e18, - borrowerCollateralization: 7.015307034516347067 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: expectedDebt, + borrowerCollateral: 50 * 1e18, + borrowert0Np: 448.381722115384615591 * 1e18, + borrowerCollateralization: 7.015307034516347067 * 1e18 + }); } /** @@ -577,111 +540,90 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { */ function testPoolBorrowReverts() external tearDown { // should revert if borrower attempts to borrow with an out of bounds limitIndex - _assertBorrowLimitIndexRevert( - { - from: _borrower, - amount: 1_000 * 1e18, - indexLimit: 1000 - } - ); + _assertBorrowLimitIndexRevert({ + from: _borrower, + amount: 1_000 * 1e18, + indexLimit: 1000 + }); // should revert if borrower tries to borrow on behalf of different address - _assertBorrowBorrowerNotSenderRevert( - { - from: _borrower, - borrower: _borrower2, - amount: 1 * 1e18, - indexLimit: 7000 - } - ); + _assertBorrowBorrowerNotSenderRevert({ + from: _borrower, + borrower: _borrower2, + amount: 1 * 1e18, + indexLimit: 7000 + }); // should revert if borrower didn't pledged any collateral - _assertBorrowBorrowerUnderCollateralizedRevert( - { - from: _borrower, - amount: 500 * 1e18, - indexLimit: 3000 - } - ); + _assertBorrowBorrowerUnderCollateralizedRevert({ + from: _borrower, + amount: 500 * 1e18, + indexLimit: 3000 + }); // borrower 1 borrows 500 quote from the pool after adding sufficient collateral - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - amount: 50 * 1e18 - } - ); - _borrow( - { - from: _borrower, - amount: 500 * 1e18, - indexLimit: 3_000, - newLup: 3_010.892022197881557845 * 1e18 - } - ); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + amount: 50 * 1e18 + }); + _borrow({ + from: _borrower, + amount: 500 * 1e18, + indexLimit: 3_000, + newLup: 3_010.892022197881557845 * 1e18 + }); // borrower 2 borrows 15k quote from the pool with borrower2 becoming new queue HEAD - _pledgeCollateral( - { - from: _borrower2, - borrower: _borrower2, - amount: 6 * 1e18 - } - ); - _borrow( - { - from: _borrower2, - amount: 15_000 * 1e18, - indexLimit: 3_000, - newLup: 2_995.912459898389633881 * 1e18 - } - ); + _pledgeCollateral({ + from: _borrower2, + borrower: _borrower2, + amount: 6 * 1e18 + }); + _borrow({ + from: _borrower2, + amount: 15_000 * 1e18, + indexLimit: 3_000, + newLup: 2_995.912459898389633881 * 1e18 + }); // should revert if borrower undercollateralized - _assertBorrowBorrowerUnderCollateralizedRevert( - { - from: _borrower2, - amount: 2_976 * 1e18, - indexLimit: 3000 - } - ); + _assertBorrowBorrowerUnderCollateralizedRevert({ + from: _borrower2, + amount: 2_976 * 1e18, + indexLimit: 3000 + }); // should be able to borrow if properly specified - _borrow( - { - from: _borrower2, - amount: 10 * 1e18, - indexLimit: 3_000, - newLup: 2_995.912459898389633881 * 1e18 - } - ); + _borrow({ + from: _borrower2, + amount: 10 * 1e18, + indexLimit: 3_000, + newLup: 2_995.912459898389633881 * 1e18 + }); } function testMinBorrowAmountCheck() external tearDown { // 10 borrowers draw debt for (uint i=0; i<10; ++i) { - _anonBorrowerDrawsDebt(100 * 1e18, 1_200 * 1e18, 7777); + _anonBorrowerDrawsDebt(100 * 1e18, 1_200 * 1e18, MAX_FENWICK_INDEX); } + (, uint256 loansCount, , , ) = _poolUtils.poolLoansInfo(address(_pool)); assertEq(loansCount, 10); - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - amount: 100 * 1e18 - } - ); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + amount: 100 * 1e18 + }); // should revert if borrower attempts to borrow more than minimum amount - _assertBorrowMinDebtRevert( - { - from: _borrower, - amount: 10 * 1e18, - indexLimit: 7_777 - } - ); + _assertBorrowMinDebtRevert({ + from: _borrower, + amount: 10 * 1e18, + indexLimit: MAX_FENWICK_INDEX + }); } /** @@ -695,38 +637,30 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { deal(address(_quote), _borrower, _quote.balanceOf(_borrower) + 10_000 * 1e18); // should revert if borrower has no debt - _assertRepayNoDebtRevert( - { - from: _borrower, - borrower: _borrower, - amount: 10_000 * 1e18 - } - ); + _assertRepayNoDebtRevert({ + from: _borrower, + borrower: _borrower, + amount: 10_000 * 1e18 + }); - _assertPullBorrowerNotSenderRevert( - { - from: _borrower, - borrower: _borrower2, - amount: 10_000 * 1e18 - } - ); + _assertPullBorrowerNotSenderRevert({ + from: _borrower, + borrower: _borrower2, + amount: 10_000 * 1e18 + }); // borrower 1 borrows 1000 quote from the pool - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - amount: 50 * 1e18 - } - ); - _borrow( - { - from: _borrower, - amount: 1_000 * 1e18, - indexLimit: 3_000, - newLup: 3_010.892022197881557845 * 1e18 - } - ); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + amount: 50 * 1e18 + }); + _borrow({ + from: _borrower, + amount: 1_000 * 1e18, + indexLimit: 3_000, + newLup: 3_010.892022197881557845 * 1e18 + }); _assertPool( PoolParams({ @@ -747,21 +681,17 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { ); // borrower 2 borrows 5k quote from the pool and becomes new queue HEAD - _pledgeCollateral( - { - from: _borrower2, - borrower: _borrower2, - amount: 50 * 1e18 - } - ); - _borrow( - { - from: _borrower2, - amount: 5_000 * 1e18, - indexLimit: 3_000, - newLup: 3_010.892022197881557845 * 1e18 - } - ); + _pledgeCollateral({ + from: _borrower2, + borrower: _borrower2, + amount: 50 * 1e18 + }); + _borrow({ + from: _borrower2, + amount: 5_000 * 1e18, + indexLimit: 3_000, + newLup: 3_010.892022197881557845 * 1e18 + }); _assertPool( PoolParams({ @@ -781,6 +711,14 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { }) ); + // should revert if LUP is below the limit + ( , , , , , uint256 lupIndex ) = _poolUtils.poolPricesInfo(address(_pool)); + _assertPullLimitIndexRevert({ + from: _borrower, + amount: 20 * 1e18, + indexLimit: lupIndex - 1 + }); + // should be able to repay loan if properly specified _repayDebt({ from: _borrower, @@ -813,48 +751,43 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { function testMinRepayAmountCheck() external tearDown { // borrower 1 borrows 1000 quote from the pool _drawDebt({ - from: _borrower, - borrower: _borrower, - amountToBorrow: 1_000 * 1e18, - limitIndex: 3_000, + from: _borrower, + borrower: _borrower, + amountToBorrow: 1_000 * 1e18, + limitIndex: 3_000, collateralToPledge: 50 * 1e18, - newLup: 3_010.892022197881557845 * 1e18 + newLup: 3_010.892022197881557845 * 1e18 }); // 9 other borrowers draw debt for (uint i=0; i<9; ++i) { - _anonBorrowerDrawsDebt(100 * 1e18, 1_000 * 1e18, 7777); + _anonBorrowerDrawsDebt(100 * 1e18, 1_000 * 1e18, MAX_FENWICK_INDEX); } + (, uint256 loansCount, , , ) = _poolUtils.poolLoansInfo(address(_pool)); assertEq(loansCount, 10); // should revert if amount left after repay is less than the average debt - _assertRepayMinDebtRevert( - { - from: _borrower, - borrower: _borrower, - amount: 950 * 1e18 - } - ); + _assertRepayMinDebtRevert({ + from: _borrower, + borrower: _borrower, + amount: 950 * 1e18 + }); } function testRepayLoanFromDifferentActor() external tearDown { // borrower 1 borrows 1000 quote from the pool - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - amount: 50 * 1e18 - } - ); - _borrow( - { - from: _borrower, - amount: 1_000 * 1e18, - indexLimit: 3_000, - newLup: 3_010.892022197881557845 * 1e18 - } - ); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + amount: 50 * 1e18 + }); + _borrow({ + from: _borrower, + amount: 1_000 * 1e18, + indexLimit: 3_000, + newLup: 3_010.892022197881557845 * 1e18 + }); _assertPool( PoolParams({ @@ -910,25 +843,23 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { */ function testZeroThresholdPriceLoan() external tearDown { // borrower 1 initiates a highly overcollateralized loan with a TP of 0 that won't be inserted into the Queue - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - amount: 50 * 1e18 - } - ); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + amount: 50 * 1e18 + }); + vm.expectRevert(abi.encodeWithSignature('ZeroThresholdPrice()')); IERC20Pool(address(_pool)).drawDebt(_borrower, 0.00000000000000001 * 1e18, 3000, 0); - // borrower 1 borrows 500 quote from the pool after using a non 0 TP _drawDebt({ - from: _borrower, - borrower: _borrower, - amountToBorrow: 500 * 1e18, - limitIndex: 3_000, + from: _borrower, + borrower: _borrower, + amountToBorrow: 500 * 1e18, + limitIndex: 3_000, collateralToPledge: 50 * 1e18, - newLup: 3_010.892022197881557845 * 1e18 + newLup: 3_010.892022197881557845 * 1e18 }); _assertPool( @@ -985,19 +916,19 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { }) ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 500.480769230769231 * 1e18, - borrowerCollateral: 50 * 1e18, - borrowert0Np: 10.510096153846153851 * 1e18, - borrowerCollateralization: 300.799971477982403259 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 500.480769230769231 * 1e18, + borrowerCollateral: 50 * 1e18, + borrowert0Np: 10.510096153846153851 * 1e18, + borrowerCollateralization: 300.799971477982403259 * 1e18 + }); + deal(address(_quote), _borrower, _quote.balanceOf(_borrower) + 10_000 * 1e18); + // should revert if borrower repays most, but not all of their debt resulting in a 0 tp loan remaining on the book vm.expectRevert(abi.encodeWithSignature('ZeroThresholdPrice()')); - IERC20Pool(address(_pool)).repayDebt(_borrower, 500.480769230769231000 * 1e18 - 1, 0); + IERC20Pool(address(_pool)).repayDebt(_borrower, 500.480769230769231000 * 1e18 - 1, 0, _borrower, MAX_FENWICK_INDEX); // should be able to pay back all pendingDebt _repayDebt({ @@ -1032,70 +963,66 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { // check balances before borrow assertEq(_quote.balanceOf(_lender), 150_000 * 1e18); - _assertLenderLpBalance( - { - lender: _lender, - index: highest, - lpBalance: 10_000 * 1e27, - depositTime: _startTime - } - ); + _assertLenderLpBalance({ + lender: _lender, + index: highest, + lpBalance: 10_000 * 1e18, + depositTime: _startTime + }); + assertEq(_quote.balanceOf(_borrower), 0); assertEq(_collateral.balanceOf(_borrower), 100 * 1e18); // pledge and borrow - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - amount: 100 * 1e18 - } - ); - _borrow( - { - from: _borrower, - amount: 21_000 * 1e18, - indexLimit: 3_000, - newLup: 2_981.007422784467321543 * 1e18 - } - ); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + amount: 100 * 1e18 + }); + _borrow({ + from: _borrower, + amount: 21_000 * 1e18, + indexLimit: 3_000, + newLup: 2_981.007422784467321543 * 1e18 + }); + assertEq(_quote.balanceOf(_borrower), 21_000 * 1e18); assertEq(_collateral.balanceOf(_borrower), 0); - _assertPoolPrices( - { - htp: 210.201923076923077020 * 1e18, - htpIndex: 3_083, - hpb: 3_010.892022197881557845 * 1e18, - hpbIndex: 2550, - lup: 2_981.007422784467321543 * 1e18, - lupIndex: 2_552 - } - ); + _assertPoolPrices({ + htp: 210.201923076923077020 * 1e18, + htpIndex: 3_083, + hpb: 3_010.892022197881557845 * 1e18, + hpbIndex: 2550, + lup: 2_981.007422784467321543 * 1e18, + lupIndex: 2_552 + }); + + _addLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: _indexOf(200 * 1e18), + lpAward: 10_000 * 1e18, + newLup: 2_981.007422784467321543 * 1e18 + }); + // penalty should not be applied on buckets with prices lower than PTP - _addLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: _indexOf(200 * 1e18), - lpAward: 10_000 * 1e27, - newLup: 2_981.007422784467321543 * 1e18 - } - ); + assertEq(_quote.balanceOf(_lender), 140_000 * 1e18); - _removeAllLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: _indexOf(200 * 1e18), - newLup: 2_981.007422784467321543 * 1e18, - lpRedeem: 10_000 * 1e27 - } - ); + + _removeAllLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: _indexOf(200 * 1e18), + newLup: 2_981.007422784467321543 * 1e18, + lpRedeem: 10_000 * 1e18 + }); + assertEq(_quote.balanceOf(_lender), 150_000 * 1e18); // no tokens paid as penalty // repay entire loan deal(address(_quote), _borrower, _quote.balanceOf(_borrower) + 40 * 1e18); + _repayDebt({ from: _borrower, borrower: _borrower, @@ -1104,31 +1031,32 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { collateralToPull: 0, newLup: MAX_PRICE }); + assertEq(_quote.balanceOf(_borrower), 19.807692307692298000 * 1e18); assertEq(_collateral.balanceOf(_borrower), 0); - _assertPoolPrices( - { - htp: 0, - htpIndex: 7_388, - hpb: 3_010.892022197881557845 * 1e18, - hpbIndex: 2550, - lup: MAX_PRICE, - lupIndex: 0 - } - ); + _assertPoolPrices({ + htp: 0, + htpIndex: 7_388, + hpb: 3_010.892022197881557845 * 1e18, + hpbIndex: 2550, + lup: MAX_PRICE, + lupIndex: 0 + }); + // lender removes everything from above PTP, penalty should be applied uint256 snapshot = vm.snapshot(); - _removeAllLiquidity( - { - from: _lender, - amount: 9_990.384615384615380000 * 1e18, - index: highest, - newLup: MAX_PRICE, - lpRedeem: 10_000 * 1e27 - } - ); + + _removeAllLiquidity({ + from: _lender, + amount: 9_990.384615384615380000 * 1e18, + index: highest, + newLup: MAX_PRICE, + lpRedeem: 10_000 * 1e18 + }); + assertEq(_quote.balanceOf(_lender), 159_990.384615384615380000 * 1e18); // 5 tokens paid as penalty + vm.revertTo(snapshot); // borrower pulls first all their collateral pledged, PTP goes to 0, penalty should be applied @@ -1139,30 +1067,31 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { amountRepaid: 0, collateralToPull: 100 * 1e18 }); + assertEq(_quote.balanceOf(_borrower), 19.807692307692298000 * 1e18); assertEq(_collateral.balanceOf(_borrower), 100 * 1e18); - _removeAllLiquidity( - { - from: _lender, - amount: 9_990.384615384615380000 * 1e18, - index: highest, - newLup: MAX_PRICE, - lpRedeem: 10_000 * 1e27 - } - ); + + _removeAllLiquidity({ + from: _lender, + amount: 9_990.384615384615380000 * 1e18, + index: highest, + newLup: MAX_PRICE, + lpRedeem: 10_000 * 1e18 + }); + assertEq(_quote.balanceOf(_lender), 159_990.384615384615380000 * 1e18); // 5 tokens paid as penalty // lender removes everything from price above PTP after 24 hours, penalty should not be applied skip(1 days); - _removeAllLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: med, - newLup: MAX_PRICE, - lpRedeem: 10_000 * 1e27 - } - ); + + _removeAllLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: med, + newLup: MAX_PRICE, + lpRedeem: 10_000 * 1e18 + }); + assertEq(_quote.balanceOf(_lender), 169_990.384615384615380000 * 1e18); // no tokens paid as penalty } } @@ -1192,52 +1121,42 @@ contract ERC20PoolBorrowFuzzyTest is ERC20FuzzyHelperContract { _mintQuoteAndApproveTokens(_lender, 200_000 * 1e18); _mintQuoteAndApproveTokens(_lender1, 200_000 * 1e18); - // lender deposits 10000 DAI in 5 buckets each - _addLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: highest, - lpAward: 10_000 * 1e27, - newLup: MAX_PRICE - } - ); - _addLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: high, - lpAward: 10_000 * 1e27, - newLup: MAX_PRICE - } - ); - _addLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: med, - lpAward: 10_000 * 1e27, - newLup: MAX_PRICE - } - ); - _addLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: low, - lpAward: 10_000 * 1e27, - newLup: MAX_PRICE - } - ); - _addLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: lowest, - lpAward: 10_000 * 1e27, - newLup: MAX_PRICE - } - ); + // lender deposits 10000 quote tokens in 5 buckets each + _addLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: highest, + lpAward: 10_000 * 1e18, + newLup: MAX_PRICE + }); + _addLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: high, + lpAward: 10_000 * 1e18, + newLup: MAX_PRICE + }); + _addLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: med, + lpAward: 10_000 * 1e18, + newLup: MAX_PRICE + }); + _addLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: low, + lpAward: 10_000 * 1e18, + newLup: MAX_PRICE + }); + _addLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: lowest, + lpAward: 10_000 * 1e18, + newLup: MAX_PRICE + }); _assertPool( PoolParams({ @@ -1267,22 +1186,23 @@ contract ERC20PoolBorrowFuzzyTest is ERC20FuzzyHelperContract { uint256[] memory indexes = new uint256[](numIndexes); for (uint256 i = 0; i < numIndexes; ++i) { deal(address(_quote), _lender, mintAmount_); + indexes[i] = _randomIndex(); _addLiquidity({ from: _lender, amount: mintAmount_, index: indexes[i], - lpAward: mintAmount_ * 1e9, + lpAward: mintAmount_, newLup: _calculateLup(address(_pool), 0) }); _assertBucket({ index: indexes[i], - lpBalance: mintAmount_ * 1e9, + lpBalance: mintAmount_, collateral: 0, deposit: mintAmount_, - exchangeRate: 1e27 + exchangeRate: 1e18 }); } @@ -1291,7 +1211,9 @@ contract ERC20PoolBorrowFuzzyTest is ERC20FuzzyHelperContract { uint256 limitIndex = _findLowestIndexPrice(indexes); uint256 borrowAmount = Maths.wdiv(mintAmount_, Maths.wad(3)); uint256 requiredCollateral = _requiredCollateral(Maths.wdiv(mintAmount_, Maths.wad(3)), limitIndex); + deal(address(_collateral), _borrower, requiredCollateral); + _drawDebt({ from: _borrower, borrower: _borrower, @@ -1305,10 +1227,10 @@ contract ERC20PoolBorrowFuzzyTest is ERC20FuzzyHelperContract { for (uint256 i = 0; i < numIndexes; ++i) { _assertBucket({ index: indexes[i], - lpBalance: mintAmount_ * 1e9, + lpBalance: mintAmount_, collateral: 0, deposit: mintAmount_, - exchangeRate: 1e27 + exchangeRate: 1e18 }); } @@ -1318,6 +1240,7 @@ contract ERC20PoolBorrowFuzzyTest is ERC20FuzzyHelperContract { // check pool state (uint256 minDebt, , uint256 poolActualUtilization, uint256 poolTargetUtilization) = _poolUtils.poolUtilizationInfo(address(_pool)); + _assertPool( PoolParams({ htp: Maths.wdiv(debt, requiredCollateral), @@ -1335,6 +1258,7 @@ contract ERC20PoolBorrowFuzzyTest is ERC20FuzzyHelperContract { interestRateUpdate: _startTime }) ); + assertLt(_htp(), _poolUtils.lup(address(_pool))); assertGt(minDebt, 0); assertEq(_poolUtils.lup(address(_pool)), _calculateLup(address(_pool), debt)); @@ -1344,7 +1268,9 @@ contract ERC20PoolBorrowFuzzyTest is ERC20FuzzyHelperContract { // repay all debt and withdraw collateral (debt, , ) = _poolUtils.borrowerInfo(address(_pool), address(_borrower)); + deal(address(_quote), _borrower, debt); + _repayDebt({ from: _borrower, borrower: _borrower, @@ -1361,16 +1287,17 @@ contract ERC20PoolBorrowFuzzyTest is ERC20FuzzyHelperContract { // check that only deposits above the htp earned interest if (indexes[i] <= _poolUtils.priceToIndex(Maths.wdiv(debt, requiredCollateral))) { assertGt(deposit, mintAmount_); - assertGt(exchangeRate, 1e27); + assertGt(exchangeRate, 1e18); } else { assertEq(deposit, mintAmount_); - assertEq(exchangeRate, 1e27); + assertEq(exchangeRate, 1e18); } - assertEq(lpAccumulator, mintAmount_ * 1e9); + assertEq(lpAccumulator, mintAmount_); + _assertBucket({ index: indexes[i], - lpBalance: mintAmount_ * 1e9, + lpBalance: mintAmount_, collateral: 0, deposit: deposit, exchangeRate: exchangeRate diff --git a/tests/forge/ERC20Pool/ERC20PoolCollateral.t.sol b/tests/forge/ERC20Pool/ERC20PoolCollateral.t.sol index 6d698e413..f88d638ad 100644 --- a/tests/forge/ERC20Pool/ERC20PoolCollateral.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolCollateral.t.sol @@ -29,30 +29,24 @@ contract ERC20PoolCollateralTest is ERC20HelperContract { /** * @notice With 1 lender and 1 borrower test pledgeCollateral, borrow, and pullCollateral. */ - function testAddPullCollateral() external tearDown { + function testPledgeAndPullCollateral() external tearDown { // lender deposits 10000 Quote into 3 buckets - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2550 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2551 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2552 - } - ); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2550 + }); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2551 + }); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2552 + }); _assertPool( PoolParams({ @@ -71,24 +65,21 @@ contract ERC20PoolCollateralTest is ERC20HelperContract { interestRateUpdate: _startTime }) ); + assertEq(_collateral.balanceOf(_borrower), 150 * 1e18); // borrower pledge 100 collateral and get a 21_000 Quote loan - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - amount: 100 * 1e18 - } - ); - _borrow( - { - from: _borrower, - amount: 21_000 * 1e18, - indexLimit: 3_000, - newLup: 2_981.007422784467321543 * 1e18 - } - ); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + amount: 100 * 1e18 + }); + _borrow({ + from: _borrower, + amount: 21_000 * 1e18, + indexLimit: 3_000, + newLup: 2_981.007422784467321543 * 1e18 + }); _assertPool( PoolParams({ @@ -107,15 +98,14 @@ contract ERC20PoolCollateralTest is ERC20HelperContract { interestRateUpdate: _startTime }) ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 21_020.192307692307702000 * 1e18, - borrowerCollateral: 100 * 1e18, - borrowert0Np: 220.712019230769230871 * 1e18, - borrowerCollateralization: 14.181637252165253251 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 21_020.192307692307702000 * 1e18, + borrowerCollateral: 100 * 1e18, + borrowert0Np: 220.712019230769230871 * 1e18, + borrowerCollateralization: 14.181637252165253251 * 1e18 + }); + assertEq(_collateral.balanceOf(_borrower), 50 * 1e18); // pass time to allow interest to accrue @@ -147,15 +137,14 @@ contract ERC20PoolCollateralTest is ERC20HelperContract { interestRateUpdate: _startTime + 10 days }) ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 21_049.006823139002918431 * 1e18, - borrowerCollateral: 50 * 1e18, - borrowert0Np: 441.424038461538461742 * 1e18, - borrowerCollateralization: 7.081111825921092812 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 21_049.006823139002918431 * 1e18, + borrowerCollateral: 50 * 1e18, + borrowert0Np: 441.424038461538461742 * 1e18, + borrowerCollateralization: 7.081111825921092812 * 1e18 + }); + assertEq(_collateral.balanceOf(_borrower), 100 * 1e18); // remove all of the remaining claimable collateral @@ -184,39 +173,150 @@ contract ERC20PoolCollateralTest is ERC20HelperContract { interestRateUpdate: _startTime + 10 days }) ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 21_049.006823139002918431 * 1e18, - borrowerCollateral: 7.061038044473493202 * 1e18, - borrowert0Np: 3_140.657612229160876676 * 1e18, - borrowerCollateralization: 1 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 21_049.006823139002918431 * 1e18, + borrowerCollateral: 7.061038044473493202 * 1e18, + borrowert0Np: 3_140.657612229160876676 * 1e18, + borrowerCollateralization: 1 * 1e18 + }); + assertEq(_collateral.balanceOf(_borrower), 142.938961955526506798 * 1e18); } + /** + * @notice With 1 lender and 1 borrower test pledgeCollateral, borrow, pull and transfer collateral to a different recipient. + */ + function testPledgeAndPullCollateralToDifferentRecipient() external tearDown { + // lender deposits 10000 Quote into 3 buckets + + address collateralReceiver = makeAddr("receiver"); + + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2550 + }); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2551 + }); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2552 + }); + + assertEq(_collateral.balanceOf(collateralReceiver), 0); + assertEq(_collateral.balanceOf(_borrower), 150 * 1e18); + + // borrower pledge 100 collateral and get a 21_000 Quote loan + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + amount: 100 * 1e18 + }); + _borrow({ + from: _borrower, + amount: 21_000 * 1e18, + indexLimit: 3_000, + newLup: 2_981.007422784467321543 * 1e18 + }); + + _assertPool( + PoolParams({ + htp: 210.201923076923077020 * 1e18, + lup: 2_981.007422784467321543 * 1e18, + poolSize: 30_000 * 1e18, + pledgedCollateral: 100 * 1e18, + encumberedCollateral: 7.051372011699988577 * 1e18, + poolDebt: 21_020.192307692307702000 * 1e18, + actualUtilization: 0.700673076923076923 * 1e18, + targetUtilization: 1e18, + minDebtAmount: 2_102.019230769230770200 * 1e18, + loans: 1, + maxBorrower: _borrower, + interestRate: 0.05 * 1e18, + interestRateUpdate: _startTime + }) + ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 21_020.192307692307702000 * 1e18, + borrowerCollateral: 100 * 1e18, + borrowert0Np: 220.712019230769230871 * 1e18, + borrowerCollateralization: 14.181637252165253251 * 1e18 + }); + + assertEq(_collateral.balanceOf(collateralReceiver), 0); + assertEq(_collateral.balanceOf(_borrower), 50 * 1e18); + + // pass time to allow interest to accrue + skip(10 days); + + // remove some of the collateral and transfer to recipient + _repayDebtAndPullToRecipient({ + from: _borrower, + borrower: _borrower, + recipient: collateralReceiver, + amountToRepay: 0, + amountRepaid: 0, + collateralToPull: 50 * 1e18, + newLup: 2_981.007422784467321543 * 1e18 + }); + + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 21_049.006823139002918431 * 1e18, + borrowerCollateral: 50 * 1e18, + borrowert0Np: 441.424038461538461742 * 1e18, + borrowerCollateralization: 7.081111825921092812 * 1e18 + }); + + assertEq(_collateral.balanceOf(collateralReceiver), 50 * 1e18); + assertEq(_collateral.balanceOf(_borrower), 50 * 1e18); + + // remove all of the remaining claimable collateral + _repayDebtAndPullToRecipient({ + from: _borrower, + borrower: _borrower, + recipient: collateralReceiver, + amountToRepay: 0, + amountRepaid: 0, + collateralToPull: 50 * 1e18 - _encumberance(21_049.006823139002918431 * 1e18, _lup()), + newLup: 2_981.007422784467321543 * 1e18 + }); + + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 21_049.006823139002918431 * 1e18, + borrowerCollateral: 7.061038044473493202 * 1e18, + borrowert0Np: 3_140.657612229160876676 * 1e18, + borrowerCollateralization: 1 * 1e18 + }); + + assertEq(_collateral.balanceOf(collateralReceiver), 92.938961955526506798 * 1e18); + assertEq(_collateral.balanceOf(_borrower), 50 * 1e18); + } + /** * @notice 1 borrower tests reverts in pullCollateral. * Reverts: * Attempts to remove more than available claimable collateral. */ function testPullCollateralRequireEnoughCollateral() external tearDown { - _assertPullInsufficientCollateralRevert( - { - from: _borrower, - amount: 100 * 1e18 - } - ); + _assertPullInsufficientCollateralRevert({ + from: _borrower, + amount: 100 * 1e18 + }); // borrower deposits 100 collateral - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - amount: 100 * 1e18 - } - ); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + amount: 100 * 1e18 + }); // should be able to now remove collateral _repayDebtNoLupCheck({ @@ -236,105 +336,90 @@ contract ERC20PoolCollateralTest is ERC20HelperContract { _mintCollateralAndApproveTokens(_bidder, 100 * 1e18); // should revert if adding collateral at index 0 - _assertAddCollateralAtIndex0Revert( - { - from: _bidder, - amount: 4 * 1e18 - } - ); + _assertAddCollateralAtIndex0Revert({ + from: _bidder, + amount: 4 * 1e18 + }); // actor deposits collateral into a bucket - _addCollateral( - { - from: _bidder, - amount: 4 * 1e18, - index: 2550, - lpAward: 12_043.56808879152623138 * 1e27 - } - ); + _addCollateral({ + from: _bidder, + amount: 4 * 1e18, + index: 2550, + lpAward: 12_043.56808879152623138 * 1e18 + }); // check bucket state and bidder's LPs - _assertBucket( - { - index: 2550, - lpBalance: 12_043.56808879152623138 * 1e27, - collateral: 4 * 1e18, - deposit: 0, - exchangeRate: 1 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _bidder, - index: 2550, - lpBalance: 12_043.56808879152623138 * 1e27, - depositTime: _startTime - } - ); + _assertBucket({ + index: 2550, + lpBalance: 12_043.56808879152623138 * 1e18, + collateral: 4 * 1e18, + deposit: 0, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _bidder, + index: 2550, + lpBalance: 12_043.56808879152623138 * 1e18, + depositTime: _startTime + }); + // check balances assertEq(_collateral.balanceOf(_bidder), 96 * 1e18); assertEq(_collateral.balanceOf(address(_pool)), 4 * 1e18); assertEq(_quote.balanceOf(address(_pool)), 0); // actor withdraws some of their collateral - _removeCollateral( - { - from: _bidder, - amount: 1.53 * 1e18, - index: 2550, - lpRedeem: 4_606.664793962758783502850000000 * 1e27 - } - ); + _removeCollateral({ + from: _bidder, + amount: 1.53 * 1e18, + index: 2550, + lpRedeem: 4_606.664793962758783503 * 1e18 + }); + // check bucket state and bidder's LPs - _assertBucket( - { - index: 2550, - lpBalance: 7_436.90329482876744787715 * 1e27, - collateral: 2.47 * 1e18, - deposit: 0, - exchangeRate: 1 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _bidder, - index: 2550, - lpBalance: 7_436.90329482876744787715 * 1e27, - depositTime: _startTime - } - ); + _assertBucket({ + index: 2550, + lpBalance: 7_436.903294828767447877 * 1e18, + collateral: 2.47 * 1e18, + deposit: 0, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _bidder, + index: 2550, + lpBalance: 7_436.903294828767447877 * 1e18, + depositTime: _startTime + }); + // check balances assertEq(_collateral.balanceOf(_bidder), 97.53 * 1e18); assertEq(_collateral.balanceOf(address(_pool)), 2.47 * 1e18); assertEq(_quote.balanceOf(address(_pool)), 0); // actor withdraws remainder of their _collateral - _removeCollateral( - { - from: _bidder, - amount: 2.47 * 1e18, - index: 2550, - lpRedeem: 7_436.90329482876744787715 * 1e27 - } - ); + _removeCollateral({ + from: _bidder, + amount: 2.47 * 1e18, + index: 2550, + lpRedeem: 7_436.903294828767447877 * 1e18 + }); + // check bucket state and bidder's LPs - _assertBucket( - { - index: 2550, - lpBalance: 0, - collateral: 0, - deposit: 0, - exchangeRate: 1 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _bidder, - index: 2550, - lpBalance: 0, - depositTime: _startTime - } - ); + _assertBucket({ + index: 2550, + lpBalance: 0, + collateral: 0, + deposit: 0, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _bidder, + index: 2550, + lpBalance: 0, + depositTime: _startTime + }); + // check balances assertEq(_collateral.balanceOf(_bidder), 100 * 1e18); assertEq(_collateral.balanceOf(address(_pool)), 0); @@ -346,73 +431,63 @@ contract ERC20PoolCollateralTest is ERC20HelperContract { _mintCollateralAndApproveTokens(_bidder, 1 * 1e18); // actor deposits collateral into a bucket - _addCollateral( - { - from: _bidder, - amount: 1 * 1e18, - index: 1530, - lpAward: 487616.252661175041981841 * 1e27 - } - ); + _addCollateral({ + from: _bidder, + amount: 1 * 1e18, + index: 1530, + lpAward: 487616.252661175041981841 * 1e18 + }); + + _removeCollateral({ + from: _bidder, + amount: 0.5 * 1e18, + index: 1530, + lpRedeem: 243_808.126330587520990921 * 1e18 + }); - _removeCollateral( - { - from: _bidder, - amount: 0.5 * 1e18, - index: 1530, - lpRedeem: 243_808.1263305875209909205 * 1e27 - } - ); // check bucket state and bidder's LPs - _assertBucket( - { - index: 1530, - lpBalance: 243_808.1263305875209909205 * 1e27, - collateral: 0.5 * 1e18, - deposit: 0, - exchangeRate: 1 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _bidder, - index: 1530, - lpBalance: 243_808.1263305875209909205 * 1e27, - depositTime: _startTime - } - ); + _assertBucket({ + index: 1530, + lpBalance: 243_808.126330587520990920 * 1e18, + collateral: 0.5 * 1e18, + deposit: 0, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _bidder, + index: 1530, + lpBalance: 243_808.126330587520990920 * 1e18, + depositTime: _startTime + }); + // check balances assertEq(_collateral.balanceOf(_bidder), 0.5 * 1e18); assertEq(_collateral.balanceOf(address(_pool)), 0.5 * 1e18); assertEq(_quote.balanceOf(address(_pool)), 0); // actor withdraws remainder of their _collateral - _removeAllCollateral( - { - from: _bidder, - amount: 0.5 * 1e18, - index: 1530, - lpRedeem: 243_808.1263305875209909205 * 1e27 - } - ); + _removeAllCollateral({ + from: _bidder, + amount: 0.5 * 1e18, + index: 1530, + lpRedeem: 243_808.126330587520990920 * 1e18 + }); + // check bucket state and bidder's LPs - _assertBucket( - { - index: 1530, - lpBalance: 0, - collateral: 0, - deposit: 0, - exchangeRate: 1 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _bidder, - index: 1530, - lpBalance: 0, - depositTime: _startTime - } - ); + _assertBucket({ + index: 1530, + lpBalance: 0, + collateral: 0, + deposit: 0, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _bidder, + index: 1530, + lpBalance: 0, + depositTime: _startTime + }); + // check balances assertEq(_collateral.balanceOf(_bidder), 1 * 1e18); assertEq(_collateral.balanceOf(address(_pool)), 0 * 1e18); @@ -423,42 +498,44 @@ contract ERC20PoolCollateralTest is ERC20HelperContract { uint256 testIndex = 6348; // should revert if no collateral in the bucket - _assertRemoveInsufficientCollateralRevert( - { - from: _lender, - amount: 3.50 * 1e18, - index: testIndex - } - ); + _assertRemoveInsufficientCollateralRevert({ + from: _lender, + amount: 3.50 * 1e18, + index: testIndex + }); // another actor deposits some collateral deal(address(_collateral), _bidder, 100 * 1e18); + changePrank(_bidder); _collateral.approve(address(_pool), 100 * 1e18); - _addCollateral( - { - from: _bidder, - amount: 0.65 * 1e18, - index: testIndex, - lpAward: 0.0000116119721720119 * 1e27 - } - ); + + _addCollateral({ + from: _bidder, + amount: 0.65 * 1e18, + index: testIndex, + lpAward: 0.000011611972172012 * 1e18 + }); // should revert if actor has no LPB in the bucket - _assertRemoveAllCollateralNoClaimRevert( - { - from: _lender, - index: testIndex - } - ); + _assertRemoveAllCollateralNoClaimRevert({ + from: _lender, + index: testIndex + }); // should revert if actor does not have LP - _assertRemoveAllCollateralNoClaimRevert( - { - from: _lender, - index: testIndex - } - ); + _assertRemoveAllCollateralNoClaimRevert({ + from: _lender, + index: testIndex + }); + + // should revert if expiration passed + _assertAddCollateralExpiredRevert({ + from: _lender, + amount: 0.5 * 1e18, + index: testIndex, + expiry: block.timestamp - 2 minutes + }); } function testPledgeCollateralFromDifferentActor() external tearDown { @@ -480,17 +557,16 @@ contract ERC20PoolCollateralTest is ERC20HelperContract { interestRateUpdate: _startTime }) ); + assertEq(_collateral.balanceOf(_borrower), 150 * 1e18); assertEq(_collateral.balanceOf(_borrower2), 100 * 1e18); // borrower deposits 100 collateral - _pledgeCollateral( - { - from: _borrower2, - borrower: _borrower2, - amount: 100 * 1e18 - } - ); + _pledgeCollateral({ + from: _borrower2, + borrower: _borrower2, + amount: 100 * 1e18 + }); // check pool state collateral accounting updated properly _assertPool( @@ -510,7 +586,158 @@ contract ERC20PoolCollateralTest is ERC20HelperContract { interestRateUpdate: _startTime }) ); + assertEq(_collateral.balanceOf(_borrower), 150 * 1e18); assertEq(_collateral.balanceOf(_borrower2), 0); } + + function testAddRemoveCollateralBucketExchangeRateInvariantDifferentActor() external tearDown { + _mintCollateralAndApproveTokens(_lender, 50000000000 * 1e18); + + _addInitialLiquidity({ + from: _bidder, + amount: 6879, + index: 2570 + }); + + _assertLenderLpBalance({ + lender: _lender, + index: 2570, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: _bidder, + index: 2570, + lpBalance: 6879, + depositTime: _startTime + }); + _assertBucket({ + index: 2570, + lpBalance: 6879, + collateral: 0, + deposit: 6879, + exchangeRate: 1 * 1e18 // exchange rate should not change + }); + + _addCollateral({ + from: _lender, + amount: 3642907759.282013932739218713 * 1e18, + index: 2570, + lpAward: 9927093687851.086595628225711617 * 1e18 + }); + + _assertLenderLpBalance({ + lender: _lender, + index: 2570, + lpBalance: 9927093687851.086595628225711617 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: _bidder, + index: 2570, + lpBalance: 6879, + depositTime: _startTime + }); + _assertBucket({ + index: 2570, + lpBalance: 9927093687851.086595628225718496 * 1e18, + collateral: 3642907759.282013932739218713 * 1e18, + deposit: 6879, + exchangeRate: 1 * 1e18 // exchange rate should not change + }); + + _removeAllCollateral({ + from: _lender, + amount: 3642907759.282013932739218713 * 1e18, + index: 2570, + lpRedeem: 9927093687851.086595628225711617 * 1e18 + }); + + _assertLenderLpBalance({ + lender: _lender, + index: 2570, + lpBalance: 0, // LPs should get back to same value as before add / remove collateral + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: _bidder, + index: 2570, + lpBalance: 6879, // LPs should get back to same value as before add / remove collateral + depositTime: _startTime + }); + _assertBucket({ + index: 2570, + lpBalance: 6879, + collateral: 0, + deposit: 6879, + exchangeRate: 1 * 1e18 // exchange rate should not change + }); + } + + function testAddRemoveCollateralBucketExchangeRateInvariantSameActor() external tearDown { + _mintCollateralAndApproveTokens(_lender, 50000000000 * 1e18); + + _addInitialLiquidity({ + from: _lender, + amount: 6879, + index: 2570 + }); + + _assertLenderLpBalance({ + lender: _lender, + index: 2570, + lpBalance: 6879, + depositTime: _startTime + }); + _assertBucket({ + index: 2570, + lpBalance: 6879, + collateral: 0, + deposit: 6879, + exchangeRate: 1 * 1e18 // exchange rate should not change + }); + + _addCollateral({ + from: _lender, + amount: 3642907759.282013932739218713 * 1e18, + index: 2570, + lpAward: 9927093687851.086595628225711617 * 1e18 + }); + + _assertLenderLpBalance({ + lender: _lender, + index: 2570, + lpBalance: 9927093687851.086595628225718496 * 1e18, + depositTime: _startTime + }); + _assertBucket({ + index: 2570, + lpBalance: 9927093687851.086595628225718496 * 1e18, + collateral: 3642907759.282013932739218713 * 1e18, + deposit: 6879, + exchangeRate: 1 * 1e18 // exchange rate should not change + }); + + _removeAllCollateral({ + from: _lender, + amount: 3642907759.282013932739218713 * 1e18, + index: 2570, + lpRedeem: 9927093687851.086595628225711617 * 1e18 + }); + + _assertLenderLpBalance({ + lender: _lender, + index: 2570, + lpBalance: 6879, // LPs should get back to same value as before add / remove collateral + depositTime: _startTime + }); + _assertBucket({ + index: 2570, + lpBalance: 6879, + collateral: 0, + deposit: 6879, + exchangeRate: 1 * 1e18 // exchange rate should not change + }); + } } diff --git a/tests/forge/ERC20Pool/ERC20PoolFactory.t.sol b/tests/forge/ERC20Pool/ERC20PoolFactory.t.sol index c7b1d9015..a9e8d58f9 100644 --- a/tests/forge/ERC20Pool/ERC20PoolFactory.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolFactory.t.sol @@ -3,6 +3,8 @@ pragma solidity 0.8.14; import { ERC20HelperContract } from './ERC20DSTestPlus.sol'; +import { Token } from '../utils/Tokens.sol'; + import { ERC20Pool } from 'src/ERC20Pool.sol'; import { ERC20PoolFactory } from 'src/ERC20PoolFactory.sol'; import { IPoolErrors } from 'src/interfaces/pool/commons/IPoolErrors.sol'; @@ -23,46 +25,38 @@ contract ERC20PoolFactoryTest is ERC20HelperContract { function testDeployERC20PoolWithZeroAddress() external { // should revert if trying to deploy with zero address as collateral - _assertDeployWith0xAddressRevert( - { - poolFactory: address(_poolFactory), - collateral: address(0), - quote: address(_quote), - interestRate: 0.05 * 10**18 - } - ); + _assertDeployWith0xAddressRevert({ + poolFactory: address(_poolFactory), + collateral: address(0), + quote: address(_quote), + interestRate: 0.05 * 10**18 + }); // should revert if trying to deploy with zero address as quote token - _assertDeployWith0xAddressRevert( - { - poolFactory: address(_poolFactory), - collateral: address(_collateral), - quote: address(0), - interestRate: 0.05 * 10**18 - } - ); + _assertDeployWith0xAddressRevert({ + poolFactory: address(_poolFactory), + collateral: address(_collateral), + quote: address(0), + interestRate: 0.05 * 10**18 + }); } function testDeployERC20PoolWithInvalidRate() external { // should revert if trying to deploy with interest rate lower than accepted - _assertDeployWithInvalidRateRevert( - { - poolFactory: address(_poolFactory), - collateral: address(_collateral), - quote: address(_quote), - interestRate: 10**18 - } - ); + _assertDeployWithInvalidRateRevert({ + poolFactory: address(_poolFactory), + collateral: address(_collateral), + quote: address(_quote), + interestRate: 10**18 + }); // should revert if trying to deploy with interest rate higher than accepted - _assertDeployWithInvalidRateRevert( - { - poolFactory: address(_poolFactory), - collateral: address(_collateral), - quote: address(_quote), - interestRate: 2 * 10**18 - } - ); + _assertDeployWithInvalidRateRevert({ + poolFactory: address(_poolFactory), + collateral: address(_collateral), + quote: address(_quote), + interestRate: 2 * 10**18 + }); // check tracking of deployed pools assertEq(_poolFactory.getDeployedPoolsList().length, 0); @@ -72,14 +66,12 @@ contract ERC20PoolFactoryTest is ERC20HelperContract { address poolOne = _poolFactory.deployPool(address(_collateral), address(_quote), 0.05 * 10**18); // should revert if trying to deploy same pool one more time - _assertDeployMultipleTimesRevert( - { - poolFactory: address(_poolFactory), - collateral: address(_collateral), - quote: address(_quote), - interestRate: 0.05 * 10**18 - } - ); + _assertDeployMultipleTimesRevert({ + poolFactory: address(_poolFactory), + collateral: address(_collateral), + quote: address(_quote), + interestRate: 0.05 * 10**18 + }); // should deploy different pool address poolTwo = _poolFactory.deployPool(address(_collateral), address(_collateral), 0.05 * 10**18); @@ -94,6 +86,22 @@ contract ERC20PoolFactoryTest is ERC20HelperContract { assertEq(_poolFactory.deployedPoolsList(1), poolTwo); } + function testDeployERC20PoolWithMinRate() external { + _poolFactory.deployPool(address(new Token("Collateral", "C1")), address(new Token("Quote", "Q1")), 0.01 * 10**18); + + // check tracking of deployed pools + assertEq(_poolFactory.getDeployedPoolsList().length, 1); + assertEq(_poolFactory.getNumberOfDeployedPools(), 1); + } + + function testDeployERC20PoolWithMaxRate() external { + _poolFactory.deployPool(address(new Token("Collateral", "C1")), address(new Token("Quote", "Q1")), 0.1 * 10**18); + + // check tracking of deployed pools + assertEq(_poolFactory.getDeployedPoolsList().length, 1); + assertEq(_poolFactory.getNumberOfDeployedPools(), 1); + } + function testDeployERC20Pool() external { skip(333); diff --git a/tests/forge/ERC20Pool/ERC20PoolFlashloan.t.sol b/tests/forge/ERC20Pool/ERC20PoolFlashloan.t.sol new file mode 100644 index 000000000..a316f48ad --- /dev/null +++ b/tests/forge/ERC20Pool/ERC20PoolFlashloan.t.sol @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.14; + +import '@openzeppelin/contracts/token/ERC20/ERC20.sol'; + +import { ERC20HelperContract } from './ERC20DSTestPlus.sol'; +import { + FlashloanBorrower, + SomeDefiStrategy, + SomeDefiStrategyWithRepayment +} from '../utils/FlashloanBorrower.sol'; + +import 'src/libraries/helpers/PoolHelper.sol'; +import 'src/ERC20Pool.sol'; +import 'src/ERC20PoolFactory.sol'; + +import { IPoolErrors } from 'src/interfaces/pool/IPool.sol'; + +contract ERC20PoolFlashloanTest is ERC20HelperContract { + address internal _borrower; + address internal _lender; + uint internal _bucketId; + uint internal _bucketPrice; + + function setUp() external { + _lender = makeAddr("lender"); + _borrower = makeAddr("borrower"); + + _mintQuoteAndApproveTokens(_lender, 100_000 * 1e18); + _mintQuoteAndApproveTokens(_borrower, 5_000 * 1e18); + _mintCollateralAndApproveTokens(_borrower, 100 * 1e18); + + // lender adds liquidity + _bucketPrice = 502.433988063349232760 * 1e18; + _bucketId = _indexOf(_bucketPrice); + assertEq(_bucketId, 2909); + + _addInitialLiquidity({ + from: _lender, + amount: 100_000 * 1e18, + index: _bucketId + }); + + // borrower draws debt + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + amount: 100 * 1e18 + }); + _borrow({ + from: _borrower, + amount: 25_000 * 1e18, + indexLimit: _bucketId, + newLup: _bucketPrice + }); + + (uint256 poolDebt,,) = _pool.debtInfo(); + assertEq(poolDebt, 25_024.038461538461550000 * 1e18); + } + + function testCollateralFlashloan() external tearDown { + skip(1 days); + uint256 loanAmount = 100 * 1e18; + assertEq(_pool.maxFlashLoan(address(_collateral)), loanAmount); + + // Create an example defi strategy + SomeDefiStrategy strategy = new SomeDefiStrategy(_collateral); + deal(address(_collateral), address(strategy), 10 * 1e18); + + // Create a flashloan borrower contract which interacts with the strategy + bytes memory strategyCalldata = abi.encodeWithSignature("makeMoney(uint256)", loanAmount); + FlashloanBorrower flasher = new FlashloanBorrower(address(strategy), strategyCalldata); + + // Run the token approvals + changePrank(address(flasher)); + _collateral.approve(address(_pool), loanAmount); + _collateral.approve(address(strategy), loanAmount); + + // Use a flashloan to interact with the strategy + assertEq(_collateral.balanceOf(address(flasher)), 0); + assertTrue(!flasher.callbackInvoked()); + _pool.flashLoan(flasher, address(_collateral), loanAmount, new bytes(0)); + assertTrue(flasher.callbackInvoked()); + assertEq(_collateral.balanceOf(address(flasher)), 3.5 * 1e18); + } + + function testFlashloanFee() external tearDown { + uint256 loanAmount = 100 * 1e18; + + // Ensure there is no fee for quote token + uint256 fee = _pool.flashFee(address(_quote), loanAmount); + assertEq(fee, 0); + + // Ensure there is no fee for collateral + fee = _pool.flashFee(address(_collateral), loanAmount); + assertEq(fee, 0); + + // Ensure fee reverts for a random address which isn't a token + _assertFlashloanFeeRevertsForToken(makeAddr("nobody"), loanAmount); + } + + function testMaxFlashloan() external tearDown { + assertEq(_pool.maxFlashLoan(_pool.quoteTokenAddress()), 75_000 * 1e18); + assertEq(_pool.maxFlashLoan(_pool.collateralAddress()), 100 * 1e18); + assertEq(_pool.maxFlashLoan(makeAddr("nobody")), 0); + } + + function testCannotFlashloanMoreCollateralThanAvailable() external tearDown { + FlashloanBorrower flasher = new FlashloanBorrower(address(0), new bytes(0)); + + // Cannot flashloan less than pool size but more than available quote token + _assertFlashloanTooLargeRevert(flasher, _pool.quoteTokenAddress(), 90_000 * 1e18); + + // Cannot flashloan more collateral than pledged + _assertFlashloanTooLargeRevert(flasher, _pool.collateralAddress(), 150 * 1e18); + } + + function testCannotFlashloanNonToken() external tearDown { + FlashloanBorrower flasher = new FlashloanBorrower(address(0), new bytes(0)); + + // Cannot flashloan a random address which isn't a token + _assertFlashloanUnavailableForToken(flasher, makeAddr("nobody"), 1); + } + + function testCallbackFailure() external tearDown { + uint256 loanAmount = 100 * 1e18; + + // Create an example defi strategy + SomeDefiStrategy strategy = new SomeDefiStrategy(_collateral); + + // Create a flashloan borrower contract which invokes a non-existant method on the strategy + bytes memory strategyCalldata = abi.encodeWithSignature("missing()"); + FlashloanBorrower flasher = new FlashloanBorrower(address(strategy), strategyCalldata); + + // Run approvals + changePrank(address(flasher)); + _quote.approve(address(_pool), loanAmount); + + // Make a failed attempt to interact with the strategy + vm.expectRevert(IPoolErrors.FlashloanCallbackFailed.selector); + _pool.flashLoan(flasher, address(_collateral), loanAmount, new bytes(0)); + assertFalse(flasher.callbackInvoked()); + } + + function testIncorrectBalanceAfterFlashloanFailure() external tearDown { + skip(1 days); + uint256 loanAmount = 100 * 1e18; + assertEq(_pool.maxFlashLoan(address(_collateral)), loanAmount); + + // Create an example defi strategy that pays a fee to pool contract + SomeDefiStrategyWithRepayment strategy = new SomeDefiStrategyWithRepayment(_collateral, address(_pool)); + deal(address(_collateral), address(strategy), 10 * 1e18); + + // Create a flashloan borrower contract which interacts with the strategy + bytes memory strategyCalldata = abi.encodeWithSignature("makeMoney(uint256)", loanAmount); + FlashloanBorrower flasher = new FlashloanBorrower(address(strategy), strategyCalldata); + + // Run the token approvals + changePrank(address(flasher)); + _collateral.approve(address(_pool), loanAmount); + _collateral.approve(address(strategy), loanAmount); + + // should revert as the pool balance after flashloan is different than the initial balance + vm.expectRevert(IPoolErrors.FlashloanIncorrectBalance.selector); + _pool.flashLoan(flasher, address(_collateral), loanAmount, new bytes(0)); + } +} + +contract ERC20PoolFlashloanPrecisionTest is ERC20HelperContract { + + ERC20 WBTC = ERC20(0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599); + ERC20 USDC = ERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); + + address internal _borrower; + address internal _lender; + + function setUp() external { + _pool = ERC20Pool(new ERC20PoolFactory(_ajna).deployPool(address(WBTC), address(USDC), 0.05 * 10**18)); + + _borrower = makeAddr("borrower"); + _lender = makeAddr("lender"); + + deal(address(WBTC), _borrower, 10 * 1e8); + + deal(address(USDC), _lender, 10_000 * 1e6); + + vm.startPrank(_borrower); + WBTC.approve(address(_pool), 10 * 1e18); + + changePrank(_lender); + USDC.approve(address(_pool), 10_000 * 1e18); + + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2500 + }); + + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + amount: 10 * 1e18 + }); + } + + function testWbtcFlashloan() external tearDown { + skip(1 days); + uint256 loanAmount = 10 * 1e8; + assertEq(_pool.maxFlashLoan(address(WBTC)), 10 * 1e8); + + // Create an example defi strategy + SomeDefiStrategy strategy = new SomeDefiStrategy(WBTC); + deal(address(WBTC), address(strategy), 10 * 1e8); + + // Create a flashloan borrower contract which interacts with the strategy + bytes memory strategyCalldata = abi.encodeWithSignature("makeMoney(uint256)", loanAmount); + FlashloanBorrower flasher = new FlashloanBorrower(address(strategy), strategyCalldata); + + // Run the token approvals + changePrank(address(flasher)); + WBTC.approve(address(_pool), loanAmount); + WBTC.approve(address(strategy), loanAmount); + + // cannot flashloan more than available in pool (by specifying pool instead collateral precision) + vm.expectRevert('SafeERC20: low-level call failed'); + _pool.flashLoan(flasher, address(WBTC), 10 * 1e18, new bytes(0)); + + // Use a flashloan to interact with the strategy + assertEq(WBTC.balanceOf(address(flasher)), 0); + assertTrue(!flasher.callbackInvoked()); + _pool.flashLoan(flasher, address(WBTC), loanAmount, new bytes(0)); + assertTrue(flasher.callbackInvoked()); + assertEq(WBTC.balanceOf(address(flasher)), 0.35 * 1e8); + } + + function testUsdcFlashloan() external tearDown { + skip(1 days); + uint256 loanAmount = 10_000 * 1e6; + assertEq(_pool.maxFlashLoan(address(USDC)), loanAmount); + + // Create an example defi strategy which produces enough yield to pay the fee + SomeDefiStrategy strategy = new SomeDefiStrategy(USDC); + deal(address(USDC), address(strategy), 10_000 * 1e6); + + // Create a flashloan borrower contract which interacts with the strategy + bytes memory strategyCalldata = abi.encodeWithSignature("makeMoney(uint256)", loanAmount); + FlashloanBorrower flasher = new FlashloanBorrower(address(strategy), strategyCalldata); + + // Run approvals + changePrank(address(flasher)); + USDC.approve(address(_pool), loanAmount); + USDC.approve(address(strategy), loanAmount); + + // cannot flashloan more than available in pool (by specifying pool instead quote token precision) + vm.expectRevert('ERC20: transfer amount exceeds balance'); + _pool.flashLoan(flasher, address(USDC), 10_000 * 1e18, new bytes(0)); + + // Use a flashloan to interact with the strategy + assertEq(USDC.balanceOf(address(flasher)), 0); + assertTrue(!flasher.callbackInvoked()); + _pool.flashLoan(flasher, address(USDC), loanAmount, new bytes(0)); + assertTrue(flasher.callbackInvoked()); + assertEq(USDC.balanceOf(address(flasher)), 350 * 1e6); + } + +} \ No newline at end of file diff --git a/tests/forge/ERC20Pool/ERC20PoolGasLoadTest.t.sol b/tests/forge/ERC20Pool/ERC20PoolGasLoadTest.t.sol index 23e301817..bd028a147 100644 --- a/tests/forge/ERC20Pool/ERC20PoolGasLoadTest.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolGasLoadTest.t.sol @@ -45,15 +45,13 @@ contract ERC20PoolGasLoadTest is ERC20DSTestPlus { _mintQuoteAndApproveTokens(lender, 200_000 * 1e18); vm.startPrank(lender); - _pool.addQuoteToken(100_000 * 1e18, 7388 - i); - _pool.addQuoteToken(100_000 * 1e18, 1 + i); + _pool.addQuoteToken(100_000 * 1e18, 7388 - i, block.timestamp + 2 minutes); + _pool.addQuoteToken(100_000 * 1e18, 1 + i, block.timestamp + 2 minutes); vm.stopPrank(); _lenders.push(lender); - unchecked { - ++i; - } + unchecked { ++i; } } } @@ -69,9 +67,8 @@ contract ERC20PoolGasLoadTest is ERC20DSTestPlus { vm.stopPrank(); _borrowers.push(borrower); - unchecked { - ++i; - } + + unchecked { ++i; } } } @@ -105,9 +102,11 @@ contract ERC20PoolCommonActionsGasLoadTest is ERC20PoolGasLoadTest { vm.assume(borrowerId_ <= LOANS_COUNT); address borrower = _borrowers[borrowerId_]; + skip(15 hours); + vm.prank(borrower); - ERC20Pool(address(_pool)).repayDebt(borrower, 100 * 1e18, 0); + ERC20Pool(address(_pool)).repayDebt(borrower, 100 * 1e18, 0, borrower, MAX_FENWICK_INDEX); assertEq(_noOfLoans(), LOANS_COUNT); } @@ -116,11 +115,14 @@ contract ERC20PoolCommonActionsGasLoadTest is ERC20PoolGasLoadTest { assertEq(_noOfLoans(), LOANS_COUNT); vm.assume(borrowerId_ <= LOANS_COUNT); + skip(15 hours); + address borrower = _borrowers[borrowerId_]; (uint256 debt, , ) = _poolUtils.borrowerInfo(address(_pool), borrower); + vm.prank(borrower); - ERC20Pool(address(_pool)).repayDebt(borrower, debt, 0); + ERC20Pool(address(_pool)).repayDebt(borrower, debt, 0, borrower, MAX_FENWICK_INDEX); assertEq(_noOfLoans(), LOANS_COUNT - 1); } @@ -129,18 +131,20 @@ contract ERC20PoolCommonActionsGasLoadTest is ERC20PoolGasLoadTest { assertEq(_noOfLoans(), LOANS_COUNT); vm.assume(borrowerId_ <= LOANS_COUNT); + skip(15 hours); + address borrower = _borrowers[borrowerId_]; + vm.prank(borrower); - _drawDebtNoLupCheck( - { - from: borrower, - borrower: borrower, - amountToBorrow: 1_000 * 1e18, - limitIndex: 5000, - collateralToPledge: 0 - } - ); + + _drawDebtNoLupCheck({ + from: borrower, + borrower: borrower, + amountToBorrow: 1_000 * 1e18, + limitIndex: 5000, + collateralToPledge: 0 + }); assertEq(_noOfLoans(), LOANS_COUNT); } @@ -156,31 +160,33 @@ contract ERC20PoolCommonActionsGasLoadTest is ERC20PoolGasLoadTest { _mintCollateralAndApproveTokens(newBorrower, 2_000 * 1e18); vm.startPrank(newBorrower); + skip(15 hours); - _drawDebtNoLupCheck( - { - from: newBorrower, - borrower: newBorrower, - amountToBorrow: 0, - limitIndex: 0, - collateralToPledge: 1_000 * 1e18 - } - ); + + _drawDebtNoLupCheck({ + from: newBorrower, + borrower: newBorrower, + amountToBorrow: 0, + limitIndex: 0, + collateralToPledge: 1_000 * 1e18 + }); + + skip(15 hours); - _drawDebtNoLupCheck( - { - from: newBorrower, - borrower: newBorrower, - amountToBorrow: 1_000 * 1e18, - limitIndex: 5000, - collateralToPledge: 0 - } - ); + _drawDebtNoLupCheck({ + from: newBorrower, + borrower: newBorrower, + amountToBorrow: 1_000 * 1e18, + limitIndex: 5000, + collateralToPledge: 0 + }); + vm.stopPrank(); assertEq(_noOfLoans(), LOANS_COUNT + 1); vm.revertTo(snapshot); + assertEq(_noOfLoans(), LOANS_COUNT); } @@ -193,10 +199,12 @@ contract ERC20PoolCommonActionsGasLoadTest is ERC20PoolGasLoadTest { assertEq(_noOfLoans(), LOANS_COUNT); address borrower = _borrowers[i]; + vm.prank(borrower); - ERC20Pool(address(_pool)).repayDebt(borrower, 100 * 1e18, 0); + ERC20Pool(address(_pool)).repayDebt(borrower, 100 * 1e18, 0, borrower, MAX_FENWICK_INDEX); assertEq(_noOfLoans(), LOANS_COUNT); + vm.revertTo(snapshot); } @@ -213,10 +221,12 @@ contract ERC20PoolCommonActionsGasLoadTest is ERC20PoolGasLoadTest { address borrower = _borrowers[i]; (uint256 debt, , ) = _poolUtils.borrowerInfo(address(_pool), borrower); + vm.prank(borrower); - ERC20Pool(address(_pool)).repayDebt(borrower, debt, 0); + ERC20Pool(address(_pool)).repayDebt(borrower, debt, 0, borrower, MAX_FENWICK_INDEX); assertEq(_noOfLoans(), LOANS_COUNT - 1); + vm.revertTo(snapshot); } @@ -228,20 +238,23 @@ contract ERC20PoolCommonActionsGasLoadTest is ERC20PoolGasLoadTest { for (uint256 i; i < LOANS_COUNT; i++) { uint256 snapshot = vm.snapshot(); + skip(15 hours); + assertEq(_noOfLoans(), LOANS_COUNT); address borrower = _borrowers[i]; - _drawDebtNoLupCheck( - { - from: borrower, - borrower: borrower, - amountToBorrow: 1_000 * 1e18, - limitIndex: 5000, - collateralToPledge: 0 - } - ); + + _drawDebtNoLupCheck({ + from: borrower, + borrower: borrower, + amountToBorrow: 1_000 * 1e18, + limitIndex: 5000, + collateralToPledge: 0 + }); + assertEq(_noOfLoans(), LOANS_COUNT); + vm.revertTo(snapshot); } @@ -255,14 +268,19 @@ contract ERC20PoolCommonActionsGasLoadTest is ERC20PoolGasLoadTest { _mintQuoteAndApproveTokens(lender, 200_000 * 1e18); vm.startPrank(lender); + skip(15 hours); - _pool.addQuoteToken(10_000 * 1e18, index_); + _pool.addQuoteToken(10_000 * 1e18, index_, block.timestamp + 2 minutes); + skip(15 hours); _pool.removeQuoteToken(5_000 * 1e18, index_); + skip(15 hours); - _pool.moveQuoteToken(1_000 * 1e18, index_, index_ + 1); + _pool.moveQuoteToken(1_000 * 1e18, index_, index_ + 1, block.timestamp + 2 minutes); + skip(15 hours); _pool.removeQuoteToken(type(uint256).max, index_); + vm.stopPrank(); } @@ -272,16 +290,23 @@ contract ERC20PoolCommonActionsGasLoadTest is ERC20PoolGasLoadTest { for (uint256 i = 1; i < LENDERS; i++) { uint256 snapshot = vm.snapshot(); + vm.startPrank(lender); + skip(15 hours); - _pool.addQuoteToken(10_000 * 1e18, 7388 - i); + _pool.addQuoteToken(10_000 * 1e18, 7388 - i, block.timestamp + 2 minutes); + skip(15 hours); - _pool.addQuoteToken(10_000 * 1e18, 1 + i); + _pool.addQuoteToken(10_000 * 1e18, 1 + i, block.timestamp + 2 minutes); + skip(15 hours); _pool.removeQuoteToken(5_000 * 1e18, 7388 - i); + skip(15 hours); _pool.removeQuoteToken(type(uint256).max, 1 + i); + vm.stopPrank(); + vm.revertTo(snapshot); } } @@ -291,14 +316,19 @@ contract ERC20PoolCommonActionsGasLoadTest is ERC20PoolGasLoadTest { _mintQuoteAndApproveTokens(kicker, type(uint256).max); // mint enough to cover bonds vm.warp(100_000 days); + vm.startPrank(kicker); + for (uint256 i; i < LOANS_COUNT; i ++) { _pool.kick(_borrowers[i]); } + skip(2 hours); + for (uint256 i; i < LOANS_COUNT - 1; i ++) { ERC20Pool(address(_pool)).take(_borrowers[i], 100 * 1e18, kicker, new bytes(0)); } + vm.stopPrank(); } @@ -307,14 +337,19 @@ contract ERC20PoolCommonActionsGasLoadTest is ERC20PoolGasLoadTest { _mintQuoteAndApproveTokens(kicker, type(uint256).max); // mint enough to cover bonds vm.warp(100_000 days); + vm.startPrank(kicker); + for (uint256 i; i < LOANS_COUNT; i ++) { _pool.kick(_borrowers[LOANS_COUNT - 1 - i]); } + skip(2 hours); + for (uint256 i; i < LOANS_COUNT - 1; i ++) { ERC20Pool(address(_pool)).take(_borrowers[LOANS_COUNT - 1 - i], 100 * 1e18, kicker, new bytes(0)); } + vm.stopPrank(); } @@ -323,15 +358,21 @@ contract ERC20PoolCommonActionsGasLoadTest is ERC20PoolGasLoadTest { _mintQuoteAndApproveTokens(kicker, type(uint256).max); // mint enough to cover bonds vm.startPrank(kicker); - _pool.addQuoteToken(500_000_000_000_000 * 1e18, 3_000); + + _pool.addQuoteToken(500_000_000_000_000 * 1e18, 3_000, block.timestamp + 2 minutes); vm.warp(100_000 days); + _pool.kickWithDeposit(3_000); // worst case scenario, pool interest accrues + skip(80 hours); + _pool.settle(_borrowers[LOANS_COUNT - 1], 10); + // kick remaining loans with deposit to get average gas cost for (uint256 i; i < LOANS_COUNT - 1; i ++) { _pool.kickWithDeposit(3_000); } + vm.stopPrank(); } } @@ -359,19 +400,28 @@ contract ERC20PoolGasArbTakeLoadTest is ERC20PoolGasLoadTest { _mintQuoteAndApproveTokens(kicker, type(uint256).max); // mint enough to cover bonds vm.warp(100_000 days); + vm.startPrank(kicker); + for (uint256 i; i < LOANS_COUNT; i ++) { _pool.kick(_borrowers[i]); } + // add quote tokens in bucket to arb - _pool.addQuoteToken(100_000 * 1e18, 1_000); + _pool.addQuoteToken(100_000 * 1e18, 1_000, block.timestamp + 2 minutes); + vm.stopPrank(); assertEq(_noOfLoans(), 0); // assert all loans are kicked + skip(14 hours); + address taker = makeAddr("taker"); + vm.startPrank(taker); + _pool.bucketTake(_borrowers[0], depositTake_, 1_000); + vm.stopPrank(); } @@ -380,19 +430,28 @@ contract ERC20PoolGasArbTakeLoadTest is ERC20PoolGasLoadTest { _mintQuoteAndApproveTokens(kicker, type(uint256).max); // mint enough to cover bonds vm.warp(100_000 days); + vm.startPrank(kicker); + for (uint256 i; i < LOANS_COUNT; i ++) { _pool.kick(_borrowers[LOANS_COUNT - 1 - i]); } + // add quote tokens in bucket to arb - _pool.addQuoteToken(100_000 * 1e18, 1_000); + _pool.addQuoteToken(100_000 * 1e18, 1_000, block.timestamp + 2 minutes); + vm.stopPrank(); assertEq(_noOfLoans(), 0); // assert all loans are kicked + skip(14 hours); + address taker = makeAddr("taker"); + vm.startPrank(taker); + _pool.bucketTake(_borrowers[LOANS_COUNT - 1], depositTake_, 1_000); + vm.stopPrank(); } @@ -410,7 +469,9 @@ contract ERC20PoolGasArbTakeLoadTest is ERC20PoolGasLoadTest { _mintQuoteAndApproveTokens(lender, 200_000 * 1e18); vm.startPrank(lender); - _pool.addQuoteToken(200_000 * 1e18, 5000 - i); + + _pool.addQuoteToken(200_000 * 1e18, 5000 - i, block.timestamp + 2 minutes); + vm.stopPrank(); _lenders.push(lender); diff --git a/tests/forge/ERC20Pool/ERC20PoolInfoUtils.t.sol b/tests/forge/ERC20Pool/ERC20PoolInfoUtils.t.sol index 6053f0262..3d2994639 100644 --- a/tests/forge/ERC20Pool/ERC20PoolInfoUtils.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolInfoUtils.t.sol @@ -34,51 +34,41 @@ contract ERC20PoolInfoUtilsTest is ERC20HelperContract { _mintQuoteAndApproveTokens(_lender1, 200_000 * 1e18); // lender deposits 10000 DAI in 5 buckets each - _addLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: highest, - lpAward: 10_000 * 1e27, - newLup: MAX_PRICE - } - ); - _addLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: high, - lpAward: 10_000 * 1e27, - newLup: MAX_PRICE - } - ); - _addLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: med, - lpAward: 10_000 * 1e27, - newLup: MAX_PRICE - } - ); - _addLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: low, - lpAward: 10_000 * 1e27, - newLup: MAX_PRICE - } - ); - _addLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: lowest, - lpAward: 10_000 * 1e27, - newLup: MAX_PRICE - } - ); + _addLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: highest, + lpAward: 10_000 * 1e18, + newLup: MAX_PRICE + }); + _addLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: high, + lpAward: 10_000 * 1e18, + newLup: MAX_PRICE + }); + _addLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: med, + lpAward: 10_000 * 1e18, + newLup: MAX_PRICE + }); + _addLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: low, + lpAward: 10_000 * 1e18, + newLup: MAX_PRICE + }); + _addLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: lowest, + lpAward: 10_000 * 1e18, + newLup: MAX_PRICE + }); _drawDebt({ from: _borrower, @@ -114,12 +104,13 @@ contract ERC20PoolInfoUtilsTest is ERC20HelperContract { uint256 scale, uint256 exchangeRate ) = _poolUtils.bucketInfo(address(_pool), 5000); + assertEq(price, 0.014854015662334135 * 1e18); assertEq(quoteTokens, 0); assertEq(collateral, 0); assertEq(bucketLPs, 0); assertEq(scale, 1 * 1e18); - assertEq(exchangeRate, 1 * 1e27); + assertEq(exchangeRate, 1 * 1e18); ( price, @@ -132,9 +123,9 @@ contract ERC20PoolInfoUtilsTest is ERC20HelperContract { assertEq(price, 2_995.912459898389633881 * 1e18); assertEq(quoteTokens, 10_000 * 1e18); assertEq(collateral, 0); - assertEq(bucketLPs, 10_000 * 1e27); + assertEq(bucketLPs, 10_000 * 1e18); assertEq(scale, 1 * 1e18); - assertEq(exchangeRate, 1 * 1e27); + assertEq(exchangeRate, 1 * 1e18); } function testPoolInfoUtilsLoansInfo() external { @@ -161,6 +152,7 @@ contract ERC20PoolInfoUtilsTest is ERC20HelperContract { uint256 lup, uint256 lupIndex ) = _poolUtils.poolPricesInfo(address(_pool)); + assertEq(hpb, 3_010.892022197881557845 * 1e18); assertEq(hpbIndex, 2550); assertEq(htp, 210.201923076923077020 * 1e18); @@ -183,6 +175,7 @@ contract ERC20PoolInfoUtilsTest is ERC20HelperContract { uint256 auctionPrice, uint256 timeRemaining ) = _poolUtils.poolReservesInfo(address(_pool)); + assertEq(reserves, 20.192307692307702000 * 1e18); assertEq(claimableReserves, 0); assertEq(claimableReservesRemaining, 0); @@ -197,6 +190,7 @@ contract ERC20PoolInfoUtilsTest is ERC20HelperContract { uint256 poolActualUtilization, uint256 poolTargetUtilization ) = _poolUtils.poolUtilizationInfo(address(_pool)); + assertEq(poolMinDebtAmount, 2_102.019230769230770200 * 1e18); assertEq(poolCollateralization, 14.181637252165253251 * 1e18); assertEq(poolActualUtilization, 0.420403846153846154 * 1e18); @@ -220,46 +214,46 @@ contract ERC20PoolInfoUtilsTest is ERC20HelperContract { assertEq( _poolUtils.lpsToCollateral( address(_pool), - 100 * 1e27, + 100 * 1e18, high ), 0 ); + changePrank(_borrower2); - ERC20Pool(address(_pool)).addCollateral(10 * 1e18, high); + ERC20Pool(address(_pool)).addCollateral(10 * 1e18, high, block.timestamp + 5 minutes); assertEq( _poolUtils.lpsToCollateral( address(_pool), - 5 * 1e27, + 5 * 1e18, high ), 1668940620571264 ); assertEq( _poolUtils.lpsToCollateral( address(_pool), - 20 * 1e27, + 20 * 1e18, high ), 6675762482285055 ); - assertEq( _poolUtils.lpsToQuoteTokens( address(_pool), - 100 * 1e27, + 100 * 1e18, high ), 100000000000000000000 ); assertEq( _poolUtils.lpsToQuoteTokens( address(_pool), - 5 * 1e27, + 5 * 1e18, high ), 5000000000000000000 ); assertEq( _poolUtils.lpsToQuoteTokens( address(_pool), - 20 * 1e27, + 20 * 1e18, high ), 20000000000000000000 ); diff --git a/tests/forge/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol b/tests/forge/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol index 2fc2e2b44..0e4e3e4ff 100644 --- a/tests/forge/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol @@ -14,58 +14,54 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { address internal _borrower; address internal _borrower2; + address internal _borrower3; address internal _lender; address internal _lender1; + address internal _lender2; function setUp() external { _borrower = makeAddr("borrower"); _borrower2 = makeAddr("borrower2"); + _borrower3 = makeAddr("borrower3"); _lender = makeAddr("lender"); _lender1 = makeAddr("_lender1"); + _lender2 = makeAddr("_lender2"); _mintCollateralAndApproveTokens(_borrower, 10_000 * 1e18); _mintCollateralAndApproveTokens(_borrower2, 200 * 1e18); + _mintCollateralAndApproveTokens(_borrower3, 1_000_000_000 * 1e18); _mintQuoteAndApproveTokens(_lender, 200_000 * 1e18); _mintQuoteAndApproveTokens(_lender1, 200_000 * 1e18); + _mintQuoteAndApproveTokens(_lender2, 100_000_000_000_000_000 * 1e18); } function testPoolInterestRateIncreaseDecrease() external tearDown { - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2550 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 20_000 * 1e18, - index: 2551 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 20_000 * 1e18, - index: 2552 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 50_000 * 1e18, - index: 3900 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 4200 - } - ); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2550 + }); + _addInitialLiquidity({ + from: _lender, + amount: 20_000 * 1e18, + index: 2551 + }); + _addInitialLiquidity({ + from: _lender, + amount: 20_000 * 1e18, + index: 2552 + }); + _addInitialLiquidity({ + from: _lender, + amount: 50_000 * 1e18, + index: 3900 + }); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 4200 + }); skip(10 days); @@ -91,15 +87,13 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { vm.expectEmit(true, true, false, true); emit UpdateInterestRate(0.05 * 1e18, 0.055 * 1e18); - _drawDebtNoLupCheck( - { - from: _borrower, - borrower: _borrower, - amountToBorrow: 46_000 * 1e18, - limitIndex: 4_300, - collateralToPledge: 100 * 1e18 - } - ); + _drawDebtNoLupCheck({ + from: _borrower, + borrower: _borrower, + amountToBorrow: 46_000 * 1e18, + limitIndex: 4_300, + collateralToPledge: 100 * 1e18 + }); _assertPool( PoolParams({ @@ -118,12 +112,10 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { interestRateUpdate: _startTime + 10 days }) ); - _assertEMAs( - { - debtEma: 4_340.881358710158802477 * 1e18, - lupColEma: 28_103.845662221475161347 * 1e18 - } - ); + _assertEMAs({ + debtEma: 4_340.881358710158802477 * 1e18, + lupColEma: 28_103.845662221475161347 * 1e18 + }); skip(14 hours); @@ -154,16 +146,15 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { interestRateUpdate: _startTime + 10 days + 14 hours }) ); - _assertEMAs( - { - debtEma: 8_279.448467499588505755 * 1e18, - lupColEma: 53_558.163735316008374982 * 1e18 - } - ); + _assertEMAs({ + debtEma: 8_279.448467499588505755 * 1e18, + lupColEma: 53_558.163735316008374982 * 1e18 + }); vm.revertTo(snapshot); // repay entire loan deal(address(_quote), _borrower, _quote.balanceOf(_borrower) + 200 * 1e18); + _repayDebt({ from: _borrower, borrower: _borrower, @@ -190,40 +181,33 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { interestRateUpdate: _startTime + 10 days + 14 hours }) ); - _assertEMAs( - { - debtEma: 4_340.881358710158802477 * 1e18, - lupColEma: 28_103.845662221475161347 * 1e18 - } - ); + _assertEMAs({ + debtEma: 4_340.881358710158802477 * 1e18, + lupColEma: 28_103.845662221475161347 * 1e18 + }); } function testOverutilizedPoolInterestRateIncrease() external tearDown { // lender deposits 1000 - _addInitialLiquidity( - { - from: _lender, - amount: 1_000 * 1e18, - index: 3232 - } - ); + _addInitialLiquidity({ + from: _lender, + amount: 1_000 * 1e18, + index: 3232 + }); // borrower draws 9100 - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - amount: 1_500 * 1e18 - } - ); - _borrow( - { - from: _borrower, - amount: 995 * 1e18, - indexLimit: 3300, - newLup: 100.332368143282009890 * 1e18 - } - ); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + amount: 1_500 * 1e18 + }); + _borrow({ + from: _borrower, + amount: 995 * 1e18, + indexLimit: 3300, + newLup: 100.332368143282009890 * 1e18 + }); + _assertPool( PoolParams({ htp: 0.663971153846153846 * 1e18, @@ -244,15 +228,15 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { // force an interest rate update skip(13 hours); - _addLiquidity( - { - from: _lender, - amount: 0, - index: 3232, - lpAward: 0, - newLup: 100.332368143282009890 * 1e18 - } - ); + + _addLiquidity({ + from: _lender, + amount: 0, + index: 3232, + lpAward: 0, + newLup: 100.332368143282009890 * 1e18 + }); + _assertPool( PoolParams({ htp: 0.664069695689831259 * 1e18, @@ -275,30 +259,26 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { function testPoolInterestRateDecrease() external tearDown { // lender makes an initial deposit skip(1 hours); - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2873 - } - ); + + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2873 + }); + // borrower draws debt skip(2 hours); - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - amount: 10 * 1e18 - } - ); - _borrow( - { - from: _borrower, - amount: 5_000 * 1e18, - indexLimit: 3000, - newLup: 601.252968524772188572 * 1e18 - } - ); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + amount: 10 * 1e18 + }); + _borrow({ + from: _borrower, + amount: 5_000 * 1e18, + indexLimit: 3000, + newLup: 601.252968524772188572 * 1e18 + }); _assertPool( PoolParams({ @@ -323,13 +303,11 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { vm.expectEmit(true, true, false, true); emit UpdateInterestRate(0.05 * 1e18, 0.045 * 1e18); - _addLiquidityNoEventCheck( - { - from: _lender1, - amount: 1_000 * 1e18, - index: 2873 - } - ); + _addLiquidityNoEventCheck({ + from: _lender1, + amount: 1_000 * 1e18, + index: 2873 + }); _assertPool( PoolParams({ @@ -351,25 +329,21 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { } function testMinInterestRate() external tearDown { - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: _i1505_26 - } - ); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: _i1505_26 + }); // pledge a tiny amount of collateral and draw a tiny amount of debt - _drawDebt( - { - from: _borrower, - borrower: _borrower, - amountToBorrow: 0.00001 * 1e18, - limitIndex: _i1505_26, - collateralToPledge: 0.00001 * 1e18, - newLup: _p1505_26 - } - ); + _drawDebt({ + from: _borrower, + borrower: _borrower, + amountToBorrow: 0.00001 * 1e18, + limitIndex: _i1505_26, + collateralToPledge: 0.00001 * 1e18, + newLup: _p1505_26 + }); // confirm interest rate starts out at 5% _assertPool( @@ -394,17 +368,15 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { while (i < 77) { // trigger an interest accumulation skip(12 hours); - _borrowZeroAmount( - { - from: _borrower, - amount: 0, - indexLimit: _i1505_26, - newLup: _p1505_26 - } - ); - unchecked { - ++i; - } + + _borrowZeroAmount({ + from: _borrower, + amount: 0, + indexLimit: _i1505_26, + newLup: _p1505_26 + }); + + unchecked { ++i; } } // show the rate bottoms out at 10 bps @@ -428,25 +400,21 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { } function testMaxInterestRate() external tearDown { - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: _i1505_26 - } - ); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: _i1505_26 + }); // pledge a lot of collateral, but draw a tiny amount of debt - _drawDebt( - { - from: _borrower, - borrower: _borrower, - amountToBorrow: 0.00001 * 1e18, - limitIndex: _i1505_26, - collateralToPledge: 10_000 * 1e18, - newLup: _p1505_26 - } - ); + _drawDebt({ + from: _borrower, + borrower: _borrower, + amountToBorrow: 0.00001 * 1e18, + limitIndex: _i1505_26, + collateralToPledge: 10_000 * 1e18, + newLup: _p1505_26 + }); // confirm interest rate starts out at 5% _assertPool( @@ -471,17 +439,15 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { while (i < 196) { // trigger an interest accumulation skip(12 hours); - _borrowZeroAmount( - { - from: _borrower, - amount: 0, - indexLimit: _i1505_26, - newLup: _p1505_26 - } - ); - unchecked { - ++i; - } + + _borrowZeroAmount({ + from: _borrower, + amount: 0, + indexLimit: _i1505_26, + newLup: _p1505_26 + }); + + unchecked { ++i; } } // show the rate maxed out at 50000% @@ -506,46 +472,36 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { function testPendingInflator() external tearDown { // add liquidity - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2550 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2552 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 4200 - } - ); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2550 + }); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2552 + }); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 4200 + }); skip(3600); // draw debt - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - amount: 50 * 1e18 - } - ); - _borrow( - { - from: _borrower, - amount: 15_000 * 1e18, - indexLimit: 4_300, - newLup: 2_981.007422784467321543 * 1e18 - } - ); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + amount: 50 * 1e18 + }); + _borrow({ + from: _borrower, + amount: 15_000 * 1e18, + indexLimit: 4_300, + newLup: 2_981.007422784467321543 * 1e18 + }); _assertPool( PoolParams({ @@ -590,20 +546,16 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { function testPoolEMAAndTargetUtilizationUpdate() external tearDown { // add initial quote to the pool - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 3_010 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2_995 - } - ); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 3_010 + }); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2_995 + }); _assertPool( PoolParams({ @@ -622,29 +574,23 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { interestRateUpdate: _startTime }) ); - _assertEMAs( - { - debtEma: 0, - lupColEma: 0 - } - ); + _assertEMAs({ + debtEma: 0, + lupColEma: 0 + }); // borrower 1 borrows 500 quote from the pool - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - amount: 50 * 1e18 - } - ); - _borrow( - { - from: _borrower, - amount: 500 * 1e18, - indexLimit: 3_010, - newLup: 327.188250324085203338 * 1e18 - } - ); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + amount: 50 * 1e18 + }); + _borrow({ + from: _borrower, + amount: 500 * 1e18, + indexLimit: 3_010, + newLup: 327.188250324085203338 * 1e18 + }); _assertPool( PoolParams({ @@ -663,28 +609,22 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { interestRateUpdate: _startTime }) ); - _assertEMAs( - { - debtEma: 0, - lupColEma: 0 - } - ); + _assertEMAs({ + debtEma: 0, + lupColEma: 0 + }); - _pledgeCollateral( - { - from: _borrower2, - borrower: _borrower2, - amount: 50 * 1e18 - } - ); - _borrow( - { - from: _borrower2, - amount: 500 * 1e18, - indexLimit: 3_010, - newLup: 327.188250324085203338 * 1e18 - } - ); + _pledgeCollateral({ + from: _borrower2, + borrower: _borrower2, + amount: 50 * 1e18 + }); + _borrow({ + from: _borrower2, + amount: 500 * 1e18, + indexLimit: 3_010, + newLup: 327.188250324085203338 * 1e18 + }); _assertPool( PoolParams({ @@ -703,24 +643,20 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { interestRateUpdate: _startTime }) ); - _assertEMAs( - { - debtEma: 0, - lupColEma: 0 - } - ); + _assertEMAs({ + debtEma: 0, + lupColEma: 0 + }); skip(10 days); // borrower 1 borrows 500 quote from the pool - _borrow( - { - from: _borrower, - amount: 10 * 1e18, - indexLimit: 3_010, - newLup: 327.188250324085203338 * 1e18 - } - ); + _borrow({ + from: _borrower, + amount: 10 * 1e18, + indexLimit: 3_010, + newLup: 327.188250324085203338 * 1e18 + }); _assertPool( PoolParams({ @@ -739,11 +675,63 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { interestRateUpdate: _startTime }) ); - _assertEMAs( - { - debtEma: 95.440014344854493304 * 1e18, - lupColEma: 954.400143448544933043 * 1e18 - } + _assertEMAs({ + debtEma: 95.440014344854493304 * 1e18, + lupColEma: 954.400143448544933043 * 1e18 + }); + } + + function testAccruePoolInterestHtpGtMaxPrice() external tearDown { + _addLiquidityNoEventCheck({ + from: _lender2, + amount: 100_000_000_000_000_000 * 1e18, + index: 1 + }); + + _drawDebtNoLupCheck({ + from: _borrower3, + borrower: _borrower3, + amountToBorrow: 90_000_000_000_000_000 * 1e18, + limitIndex: 5000, + collateralToPledge: 90_100_000 * 1e18 + }); + + skip(100 days); + + assertGt(MAX_PRICE, _htp()); + + uint256 expectedPoolDebt = 91329091841208027.611736396814389869 * 1e18; + + _assertPool( + PoolParams({ + htp: 999850593.357807564705882353 * 1e18, + lup: 999969141.897027226245329498 * 1e18, + poolSize: 100_000_000_000_000_000 * 1e18, + pledgedCollateral: 90_100_000 * 1e18, + encumberedCollateral: 91_331_910.170696775095411340 * 1e18, + poolDebt: 91329091841208027.611736396814389869 * 1e18, + actualUtilization: 0, + targetUtilization: 1 * 1e18, + minDebtAmount: 9132909184120802.761173639681438987 * 1e18, + loans: 1, + maxBorrower: address(_borrower3), + interestRate: 0.05 * 1e18, + interestRateUpdate: _startTime + }) ); + + (uint256 poolDebt,,) = _pool.debtInfo(); + assertEq(poolDebt, expectedPoolDebt); + + // force accrue interest + _addLiquidityNoEventCheck({ + from: _lender2, + amount: 0, + index: 1 + }); + + // check that no interest earned if HTP is over the highest price bucket + (poolDebt,,) = _pool.debtInfo(); + assertEq(poolDebt, expectedPoolDebt); } } diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsArbTake.t.sol b/tests/forge/ERC20Pool/ERC20PoolLiquidationsArbTake.t.sol index a9efb84f3..c7b3ab308 100644 --- a/tests/forge/ERC20Pool/ERC20PoolLiquidationsArbTake.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolLiquidationsArbTake.t.sol @@ -28,75 +28,57 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { _mintCollateralAndApproveTokens(_lender1, 4 * 1e18); // Lender adds Quote token accross 5 prices - _addInitialLiquidity( - { - from: _lender, - amount: 2_000 * 1e18, - index: _i9_91 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 5_000 * 1e18, - index: _i9_81 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 11_000 * 1e18, - index: _i9_72 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 25_000 * 1e18, - index: _i9_62 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 30_000 * 1e18, - index: _i9_52 - } - ); - - // first borrower adds collateral token and borrows - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - amount: 2 * 1e18 - } - ); - _borrow( - { - from: _borrower, - amount: 19.25 * 1e18, - indexLimit: _i9_91, - newLup: 9.917184843435912074 * 1e18 - } - ); - - // second borrower adds collateral token and borrows - _pledgeCollateral( - { - from: _borrower2, - borrower: _borrower2, - amount: 1_000 * 1e18 - } - ); - _borrow( - { - from: _borrower2, - amount: 7_980 * 1e18, - indexLimit: _i9_72, - newLup: 9.721295865031779605 * 1e18 - } - ); + _addInitialLiquidity({ + from: _lender, + amount: 2_000 * 1e18, + index: _i9_91 + }); + _addInitialLiquidity({ + from: _lender, + amount: 5_000 * 1e18, + index: _i9_81 + }); + _addInitialLiquidity({ + from: _lender, + amount: 11_000 * 1e18, + index: _i9_72 + }); + _addInitialLiquidity({ + from: _lender, + amount: 25_000 * 1e18, + index: _i9_62 + }); + _addInitialLiquidity({ + from: _lender, + amount: 30_000 * 1e18, + index: _i9_52 + }); + + // first borrower pledge collateral and borrows + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + amount: 2 * 1e18 + }); + _borrow({ + from: _borrower, + amount: 19.25 * 1e18, + indexLimit: _i9_91, + newLup: 9.917184843435912074 * 1e18 + }); + + // second borrower adds collateral and borrows + _pledgeCollateral({ + from: _borrower2, + borrower: _borrower2, + amount: 1_000 * 1e18 + }); + _borrow({ + from: _borrower2, + amount: 7_980 * 1e18, + indexLimit: _i9_72, + newLup: 9.721295865031779605 * 1e18 + }); /*****************************/ /*** Assert pre-kick state ***/ @@ -119,43 +101,37 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { interestRateUpdate: _startTime }) ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 19.268509615384615394 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.115967548076923081 * 1e18, - borrowerCollateralization: 1.009034539679184679 * 1e18 - } - ); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 7_987.673076923076926760 * 1e18, - borrowerCollateral: 1_000 * 1e18, - borrowert0Np: 8.471136974495192174 * 1e18, - borrowerCollateralization: 1.217037273735858713 * 1e18 - } - ); - _assertReserveAuction( - { - reserves: 7.691586538461542154 * 1e18, - claimableReserves : 0, - claimableReservesRemaining: 0, - auctionPrice: 0, - timeRemaining: 0 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 19.268509615384615394 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.115967548076923081 * 1e18, + borrowerCollateralization: 1.009034539679184679 * 1e18 + }); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 7_987.673076923076926760 * 1e18, + borrowerCollateral: 1_000 * 1e18, + borrowert0Np: 8.471136974495192174 * 1e18, + borrowerCollateralization: 1.217037273735858713 * 1e18 + }); + + _assertReserveAuction({ + reserves: 7.691586538461542154 * 1e18, + claimableReserves : 0, + claimableReservesRemaining: 0, + auctionPrice: 0, + timeRemaining: 0 + }); + assertEq(_quote.balanceOf(_lender), 47_000 * 1e18); // should revert if there's no auction started - _assertArbTakeNoAuctionRevert( - { - from: _lender, - borrower: _borrower, - index: _i9_91 - } - ); + _assertArbTakeNoAuctionRevert({ + from: _lender, + borrower: _borrower, + index: _i9_91 + }); // Skip to make borrower undercollateralized skip(100 days); @@ -176,27 +152,22 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { neutralPrice: 0 }) ); - - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 19.534277977147272573 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.115967548076923081 * 1e18, - borrowerCollateralization: 0.995306391810796636 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 19.534277977147272573 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.115967548076923081 * 1e18, + borrowerCollateralization: 0.995306391810796636 * 1e18 + }); - _kick( - { - from: _lender, - borrower: _borrower, - debt: 19.778456451861613480 * 1e18, - collateral: 2 * 1e18, - bond: 0.195342779771472726 * 1e18, - transferAmount: 0.195342779771472726 * 1e18 - } - ); + _kick({ + from: _lender, + borrower: _borrower, + debt: 19.778456451861613480 * 1e18, + collateral: 2 * 1e18, + bond: 0.195342779771472726 * 1e18, + transferAmount: 0.195342779771472726 * 1e18 + }); _assertAuction( AuctionParams({ @@ -214,196 +185,163 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { neutralPrice: 10.255495938002318100 * 1e18 }) ); - - _assertKicker( - { - kicker: _lender, - claimable: 0, - locked: 0.195342779771472726 * 1e18 - } - ); + _assertKicker({ + kicker: _lender, + claimable: 0, + locked: 0.195342779771472726 * 1e18 + }); } function testArbTakeCollateralRestrict() external tearDown { skip(6.5 hours); - _assertLenderLpBalance( - { - lender: _taker, - index: _i9_91, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: _i9_91, - lpBalance: 2_000 * 1e27, - depositTime: _startTime - } - ); - _assertBucket( - { - index: _i9_91, - lpBalance: 2_000 * 1e27, - collateral: 0, - deposit: 2_027.000651340490292000 * 1e18, - exchangeRate: 1.013500325670245146000000000 * 1e27 - } - ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 19.779116873676490456 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.115967548076923081 * 1e18, - borrowerCollateralization: 0.982985835729561629 * 1e18 - } - ); + _assertLenderLpBalance({ + lender: _taker, + index: _i9_91, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: _lender, + index: _i9_91, + lpBalance: 2_000 * 1e18, + depositTime: _startTime + }); + _assertBucket({ + index: _i9_91, + lpBalance: 2_000 * 1e18, + collateral: 0, + deposit: 2_027.000651340490292000 * 1e18, + exchangeRate: 1.013500325670245146 * 1e18 + }); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 19.779116873676490456 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.115967548076923081 * 1e18, + borrowerCollateralization: 0.982985835729561629 * 1e18 + }); // add liquidity to accrue interest and update reserves before arb take - _addLiquidity( - { - from: _lender1, - amount: 1 * 1e18, - index: _i9_52, - lpAward: 0.999996826562080000190961519 * 1e27, - newLup: 9.721295865031779605 * 1e18 - } - ); - _assertBucket( - { - index: _i9_91, - lpBalance: 2_000 * 1e27, - collateral: 0, - deposit: 2_027.007083921634518000 * 1e18, - exchangeRate: 1.013503541960817259000000000 * 1e27 - } - ); - _assertReserveAuction( - { - reserves: 23.911413759224212224 * 1e18, - claimableReserves : 0, - claimableReservesRemaining: 0, - auctionPrice: 0, - timeRemaining: 0 - } - ); + _addLiquidity({ + from: _lender1, + amount: 1 * 1e18, + index: _i9_52, + lpAward: 0.99999682656208 * 1e18, + newLup: 9.721295865031779605 * 1e18 + }); + + _assertBucket({ + index: _i9_91, + lpBalance: 2_000 * 1e18, + collateral: 0, + deposit: 2_027.007083921634518000 * 1e18, + exchangeRate: 1.013503541960817259 * 1e18 + }); + _assertReserveAuction({ + reserves: 23.911413759224212224 * 1e18, + claimableReserves : 0, + claimableReservesRemaining: 0, + auctionPrice: 0, + timeRemaining: 0 + }); _assertAuction( - AuctionParams({ - borrower: _borrower, - active: true, - kicker: _lender, - bondSize: 0.195342779771472726 * 1e18, // should be the same after arb take, kicker will be rewarded with LPs - bondFactor: 0.01 * 1e18, - kickTime: block.timestamp - 6.5 hours, - kickMomp: 9.818751856078723036 * 1e18, - totalBondEscrowed: 0.195342779771472726 * 1e18, - auctionPrice: 7.251730722192532064 * 1e18, - debtInAuction: 19.779116873676490456 * 1e18, - thresholdPrice: 9.889558436838245228 * 1e18, - neutralPrice: 10.255495938002318100 * 1e18 - }) - ); - - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 19.779116873676490456 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.115967548076923081 * 1e18, - borrowerCollateralization: 0.982985835729561629 * 1e18 - } + AuctionParams({ + borrower: _borrower, + active: true, + kicker: _lender, + bondSize: 0.195342779771472726 * 1e18, // should be the same after arb take, kicker will be rewarded with LPs + bondFactor: 0.01 * 1e18, + kickTime: block.timestamp - 6.5 hours, + kickMomp: 9.818751856078723036 * 1e18, + totalBondEscrowed: 0.195342779771472726 * 1e18, + auctionPrice: 7.251730722192532064 * 1e18, + debtInAuction: 19.779116873676490456 * 1e18, + thresholdPrice: 9.889558436838245228 * 1e18, + neutralPrice: 10.255495938002318100 * 1e18 + }) ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 19.779116873676490456 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.115967548076923081 * 1e18, + borrowerCollateralization: 0.982985835729561629 * 1e18 + }); // Amount is restricted by the collateral in the loan - _arbTake( - { - from: _taker, - borrower: _borrower, - kicker: _lender, - index: _i9_91, - collateralArbed: 2 * 1e18, - quoteTokenAmount: 14.503461444385064128 * 1e18, - bondChange: 0.145034614443850641 * 1e18, - isReward: true, - lpAwardTaker: 5.259881215780552826000000000 * 1e27, - lpAwardKicker: 0.143102227509983165000000000 * 1e27 - } - ); - - _assertLenderLpBalance( - { - lender: _taker, - index: _i9_91, - lpBalance: 5.259881215780552826000000000 * 1e27, - depositTime: _startTime + 100 days + 6.5 hours - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: _i9_91, - lpBalance: 2_000.143102227509983165000000000 * 1e27, // rewarded with LPs in bucket - depositTime: _startTime + 100 days + 6.5 hours - } - ); - _assertBucket( - { - index: _i9_91, - lpBalance: 2_005.402983443290535991000000000 * 1e27, - collateral: 2 * 1e18, - deposit: 2_012.648657091693304514 * 1e18, - exchangeRate: 1.013503541960817259000463129 * 1e27 - } - ); + _arbTake({ + from: _taker, + borrower: _borrower, + kicker: _lender, + index: _i9_91, + collateralArbed: 2 * 1e18, + quoteTokenAmount: 14.503461444385064128 * 1e18, + bondChange: 0.145034614443850641 * 1e18, + isReward: true, + lpAwardTaker: 5.259881215780552826 * 1e18, + lpAwardKicker: 0.143102227509983165 * 1e18 + }); + + _assertLenderLpBalance({ + lender: _taker, + index: _i9_91, + lpBalance: 5.259881215780552826 * 1e18, + depositTime: _startTime + 100 days + 6.5 hours + }); + _assertLenderLpBalance({ + lender: _lender, + index: _i9_91, + lpBalance: 2_000.143102227509983165 * 1e18, // rewarded with LPs in bucket + depositTime: _startTime + 100 days + 6.5 hours + }); + _assertBucket({ + index: _i9_91, + lpBalance: 2_005.402983443290535991 * 1e18, + collateral: 2 * 1e18, + deposit: 2_012.648657091693304514 * 1e18, + exchangeRate: 1.013503541960817259 * 1e18 + }); // reserves should remain the same after arb take - _assertReserveAuction( - { - reserves: 25.295951940381566551 * 1e18, - claimableReserves : 0, - claimableReservesRemaining: 0, - auctionPrice: 0, - timeRemaining: 0 - } - ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 6.805228224892631302 * 1e18, - borrowerCollateral: 0, - borrowert0Np: 10.115967548076923081 * 1e18, - borrowerCollateralization: 0 - } - ); + _assertReserveAuction({ + reserves: 25.295951940381566551 * 1e18, + claimableReserves : 0, + claimableReservesRemaining: 0, + auctionPrice: 0, + timeRemaining: 0 + }); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 6.805228224892631302 * 1e18, + borrowerCollateral: 0, + borrowert0Np: 10.115967548076923081 * 1e18, + borrowerCollateralization: 0 + }); _assertAuction( - AuctionParams({ - borrower: _borrower, - active: true, - kicker: _lender, - bondSize: 0.195342779771472726 * 1e18, // bond size remains the same, kicker was rewarded with LPs - bondFactor: 0.01 * 1e18, - kickTime: block.timestamp - 6.5 hours, - kickMomp: 9.818751856078723036 * 1e18, - totalBondEscrowed: 0.195342779771472726 * 1e18, - auctionPrice: 7.251730722192532064 * 1e18, - debtInAuction: 6.805228224892631302 * 1e18, - thresholdPrice: 0, - neutralPrice: 10.255495938002318100 * 1e18 - }) + AuctionParams({ + borrower: _borrower, + active: true, + kicker: _lender, + bondSize: 0.195342779771472726 * 1e18, // bond size remains the same, kicker was rewarded with LPs + bondFactor: 0.01 * 1e18, + kickTime: block.timestamp - 6.5 hours, + kickMomp: 9.818751856078723036 * 1e18, + totalBondEscrowed: 0.195342779771472726 * 1e18, + auctionPrice: 7.251730722192532064 * 1e18, + debtInAuction: 6.805228224892631302 * 1e18, + thresholdPrice: 0, + neutralPrice: 10.255495938002318100 * 1e18 + }) ); // Arb take should fail on an auction without any remaining collateral to auction - _assertArbTakeInsufficentCollateralRevert( - { - from: _taker, - borrower: _borrower, - index: _i9_91 - } - ); + _assertArbTakeInsufficentCollateralRevert({ + from: _taker, + borrower: _borrower, + index: _i9_91 + }); } function testArbTakeDebtRestrict() external tearDown { @@ -411,103 +349,85 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { skip(5 hours); _assertAuction( - AuctionParams({ - borrower: _borrower, - active: true, - kicker: _lender, - bondSize: 0.195342779771472726 * 1e18, - bondFactor: 0.01 * 1e18, - kickTime: block.timestamp - 5 hours, - kickMomp: 9.818751856078723036 * 1e18, - totalBondEscrowed: 0.195342779771472726 * 1e18, - auctionPrice: 20.510991876004636192 * 1e18, - debtInAuction: 19.778456451861613480 * 1e18, - thresholdPrice: 9.889482233342512889 * 1e18, - neutralPrice: 10.255495938002318100 * 1e18 - }) + AuctionParams({ + borrower: _borrower, + active: true, + kicker: _lender, + bondSize: 0.195342779771472726 * 1e18, + bondFactor: 0.01 * 1e18, + kickTime: block.timestamp - 5 hours, + kickMomp: 9.818751856078723036 * 1e18, + totalBondEscrowed: 0.195342779771472726 * 1e18, + auctionPrice: 20.510991876004636192 * 1e18, + debtInAuction: 19.778456451861613480 * 1e18, + thresholdPrice: 9.889482233342512889 * 1e18, + neutralPrice: 10.255495938002318100 * 1e18 + }) ); - _addLiquidity( - { - from: _lender, - amount: 25_000 * 1e18, - index: _i1505_26, - lpAward: 25_000 * 1e27, - newLup: 1_505.263728469068226832 * 1e18 - } - ); + _addLiquidity({ + from: _lender, + amount: 25_000 * 1e18, + index: _i1505_26, + lpAward: 25_000 * 1e18, + newLup: 1_505.263728469068226832 * 1e18 + }); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 19.778964466685025779 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.115967548076923081 * 1e18, - borrowerCollateralization: 152.208547722958917634 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 19.778964466685025779 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.115967548076923081 * 1e18, + borrowerCollateralization: 152.208547722958917634 * 1e18 + }); // Amount is restricted by the debt in the loan - _arbTake( - { - from: _taker, - borrower: _borrower, - kicker: _lender, - index: _i1505_26, - collateralArbed: 1.031812215971460994 * 1e18, - quoteTokenAmount: 21.163491979352977584 * 1e18, - bondChange: 0.195342779771472726 * 1e18, - isReward: false, - lpAwardTaker: 1_531.986011313779866428534379038 * 1e27, - lpAwardKicker: 0 - } - ); - - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 0, - borrowerCollateral: 0.968187784028539006 * 1e18, - borrowert0Np: 0, - borrowerCollateralization: 1 * 1e18 - } - ); - - _assertLenderLpBalance( - { - lender: _taker, - index: _i1505_26, - lpBalance: 1_531.986011313779866428534379038 * 1e27, - depositTime: block.timestamp - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: _i1505_26, - lpBalance: 25_000 * 1e27, - depositTime: block.timestamp - } - ); - _assertBucket( - { - index: _i1505_26, - lpBalance: 26_531.986011313779866428534379038 * 1e27, - collateral: 1.031812215971460994 * 1e18, - deposit: 24_978.836508020647022417 * 1e18, - exchangeRate: 0.999999999999999999999729424 * 1e27 - } - ); - - _assertReserveAuction( - { - reserves: 25.482262302272484525 * 1e18, - claimableReserves : 0, - claimableReservesRemaining: 0, - auctionPrice: 0, - timeRemaining: 0 - } - ); + _arbTake({ + from: _taker, + borrower: _borrower, + kicker: _lender, + index: _i1505_26, + collateralArbed: 1.031812215971460994 * 1e18, + quoteTokenAmount: 21.163491979352977584 * 1e18, + bondChange: 0.195342779771472726 * 1e18, + isReward: false, + lpAwardTaker: 1_531.986011313779866429 * 1e18, + lpAwardKicker: 0 + }); + + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 0, + borrowerCollateral: 0.968187784028539006 * 1e18, + borrowert0Np: 0, + borrowerCollateralization: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _taker, + index: _i1505_26, + lpBalance: 1_531.986011313779866429 * 1e18, + depositTime: block.timestamp + }); + _assertLenderLpBalance({ + lender: _lender, + index: _i1505_26, + lpBalance: 25_000 * 1e18, + depositTime: block.timestamp + }); + _assertBucket({ + index: _i1505_26, + lpBalance: 26_531.986011313779866429 * 1e18, + collateral: 1.031812215971460994 * 1e18, + deposit: 24_978.836508020647022417 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertReserveAuction({ + reserves: 25.482262302272484525 * 1e18, + claimableReserves : 0, + claimableReservesRemaining: 0, + auctionPrice: 0, + timeRemaining: 0 + }); } function testArbTakeDepositRestrict() external tearDown { @@ -515,166 +435,127 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { skip(5 hours); _assertAuction( - AuctionParams({ - borrower: _borrower, - active: true, - kicker: _lender, - bondSize: 0.195342779771472726 * 1e18, - bondFactor: 0.01 * 1e18, - kickTime: block.timestamp - 5 hours, - kickMomp: 9.818751856078723036 * 1e18, - totalBondEscrowed: 0.195342779771472726 * 1e18, - auctionPrice: 20.510991876004636192 * 1e18, - debtInAuction: 19.778456451861613480 * 1e18, - thresholdPrice: 9.889482233342512889 * 1e18, - neutralPrice: 10.255495938002318100 * 1e18 - }) + AuctionParams({ + borrower: _borrower, + active: true, + kicker: _lender, + bondSize: 0.195342779771472726 * 1e18, + bondFactor: 0.01 * 1e18, + kickTime: block.timestamp - 5 hours, + kickMomp: 9.818751856078723036 * 1e18, + totalBondEscrowed: 0.195342779771472726 * 1e18, + auctionPrice: 20.510991876004636192 * 1e18, + debtInAuction: 19.778456451861613480 * 1e18, + thresholdPrice: 9.889482233342512889 * 1e18, + neutralPrice: 10.255495938002318100 * 1e18 + }) ); - _addLiquidity( - { - from: _lender, - amount: 15.0 * 1e18, - index: _i1505_26, - lpAward: 15.0 * 1e27, - newLup: 9.721295865031779605 * 1e18 - } - ); + _addLiquidity({ + from: _lender, + amount: 15.0 * 1e18, + index: _i1505_26, + lpAward: 15.0 * 1e18, + newLup: 9.721295865031779605 * 1e18 + }); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 19.778964466685025779 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.115967548076923081 * 1e18, - borrowerCollateralization: 0.982993410135902682 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 19.778964466685025779 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.115967548076923081 * 1e18, + borrowerCollateralization: 0.982993410135902682 * 1e18 + }); // Amount is restricted by the deposit in the bucket - _arbTake( - { - from: _taker, - borrower: _borrower, - kicker: _lender, - index: _i1505_26, - collateralArbed: 0.731315193857015473 * 1e18, - quoteTokenAmount: 15.000000000000000000 * 1e18, - bondChange: 0.15 * 1e18, - isReward: false, - lpAwardTaker: 1_085.822235391290531116686016658 * 1e27, - lpAwardKicker: 0 - } - ); + _arbTake({ + from: _taker, + borrower: _borrower, + kicker: _lender, + index: _i1505_26, + collateralArbed: 0.731315193857015473 * 1e18, + quoteTokenAmount: 14.99999999999999999 * 1e18, + bondChange: 0.15 * 1e18, + isReward: false, + lpAwardTaker: 1_085.822235391290531090 * 1e18, + lpAwardKicker: 0 + }); _assertAuction( - AuctionParams({ - borrower: _borrower, - active: false, - kicker: address(0), - bondSize: 0, - bondFactor: 0, - kickTime: 0, - kickMomp: 0, - totalBondEscrowed: 0, - auctionPrice: 0, - debtInAuction: 0, - thresholdPrice: 4.858174346779663271 * 1e18, - neutralPrice: 0 - }) - ); - - _assertBucket( - { - index: _i1505_26, - lpBalance: 1_100.822235391290531116686016658 * 1e27, - collateral: 0.731315193857015473 * 1e18, - deposit: 0, - exchangeRate: 0.999999999999999999966196099 * 1e27 - } - ); - - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 6.163491979352977583 * 1e18, - borrowerCollateral: 1.268684806142984527 * 1e18, - borrowert0Np: 5.108498139847549815 * 1e18, - borrowerCollateralization: 2.001018319047304755 * 1e18 - } - ); - - _assertLenderLpBalance( - { - lender: _taker, - index: _i1505_26, - lpBalance: 1_085.822235391290531116686016658 * 1e27, - depositTime: block.timestamp - } - ); - - _assertLenderLpBalance( - { - lender: _lender, - index: _i1505_26, - lpBalance: 15.0 * 1e27, - depositTime: block.timestamp - } + AuctionParams({ + borrower: _borrower, + active: false, + kicker: address(0), + bondSize: 0, + bondFactor: 0, + kickTime: 0, + kickMomp: 0, + totalBondEscrowed: 0, + auctionPrice: 0, + debtInAuction: 0, + thresholdPrice: 4.858174346779663271 * 1e18, + neutralPrice: 0 + }) ); + _assertBucket({ + index: _i1505_26, + lpBalance: 1_100.822235391290531090 * 1e18, + collateral: 0.731315193857015473 * 1e18, + deposit: 0, + exchangeRate: 1 * 1e18 + }); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 6.163491979352977583 * 1e18, + borrowerCollateral: 1.268684806142984527 * 1e18, + borrowert0Np: 5.057793757429320955 * 1e18, + borrowerCollateralization: 2.001018319047304755 * 1e18 + }); + _assertLenderLpBalance({ + lender: _taker, + index: _i1505_26, + lpBalance: 1_085.822235391290531090 * 1e18, + depositTime: block.timestamp + }); + _assertLenderLpBalance({ + lender: _lender, + index: _i1505_26, + lpBalance: 15.0 * 1e18, + depositTime: block.timestamp + }); } function testArbTakeGTNeutralPrice() external tearDown { skip(3 hours); - _addLiquidity( - { - from: _lender, - amount: 1_000 * 1e18, - index: _i10016, - lpAward: 1_000 * 1e27, - newLup: 9.721295865031779605 * 1e18 - } - ); - - _assertLenderLpBalance( - { - lender: _taker, - index: _i10016, - lpBalance: 0, - depositTime: 0 - } - ); - - _assertLenderLpBalance( - { - lender: _lender, - index: _i10016, - lpBalance: 1_000 * 1e27, - depositTime: block.timestamp - } - ); - - _assertBucket( - { - index: _i10016, - lpBalance: 1_000 * 1e27, - collateral: 0, - deposit: 1_000 * 1e18, - exchangeRate: 1.0 * 1e27 - } - ); - - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 19.778761259189860403 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.115967548076923081 * 1e18, - borrowerCollateralization: 0.983003509435146965 * 1e18 - } - ); - + _addLiquidity({ + from: _lender, + amount: 1_000 * 1e18, + index: _i10016, + lpAward: 1_000 * 1e18, + newLup: 9.721295865031779605 * 1e18 + }); + + _assertLenderLpBalance({ + lender: _taker, + index: _i10016, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: _lender, + index: _i10016, + lpBalance: 1_000 * 1e18, + depositTime: block.timestamp + }); + _assertBucket({ + index: _i10016, + lpBalance: 1_000 * 1e18, + collateral: 0, + deposit: 1_000 * 1e18, + exchangeRate: 1.0 * 1e18 + }); _assertAuction( AuctionParams({ borrower: _borrower, @@ -691,73 +572,51 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { neutralPrice: 10.255495938002318100 * 1e18 }) ); - - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 19.778761259189860403 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.115967548076923081 * 1e18, - borrowerCollateralization: 0.983003509435146965 * 1e18 - } - ); - - _arbTake( - { - from: _taker, - borrower: _borrower, - kicker: _lender, - index: _i10016, - collateralArbed: 0.257950403803869741 * 1e18, - quoteTokenAmount: 21.163274547333150631 * 1e18, - bondChange: 0.195342779771472726 * 1e18, - isReward: false, - lpAwardTaker: 2_562.597355112798042001349648580 * 1e27, - lpAwardKicker: 0 - } - ); - - _assertLenderLpBalance( - { - lender: _taker, - index: _i10016, - lpBalance: 2_562.597355112798042001349648580 * 1e27, // arb taker was rewarded LPBs in arbed bucket - depositTime: _startTime + 100 days + 3 hours - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: _i10016, - lpBalance: 1_000 * 1e27, - depositTime: _startTime + 100 days + 3 hours - } - ); - _assertKicker( - { - kicker: _lender, - claimable: 0, - locked: 0 // kicker was penalized - } - ); - _assertBucket( - { - index: _i10016, - lpBalance: 3_562.597355112798042001349648580 * 1e27, // LP balance in arbed bucket increased with LPs awarded for arb taker - collateral: 0.257950403803869741 * 1e18, // arbed collateral added to the arbed bucket - deposit: 978.836725452666849368 * 1e18, // quote token amount is diminished in arbed bucket - exchangeRate: 1.000000000000000000007160522 * 1e27 - } - ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 0, - borrowerCollateral: 1.742049596196130259 * 1e18, - borrowert0Np: 0, - borrowerCollateralization: 1 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 19.778761259189860403 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.115967548076923081 * 1e18, + borrowerCollateralization: 0.983003509435146965 * 1e18 + }); + + _arbTake({ + from: _taker, + borrower: _borrower, + kicker: _lender, + index: _i10016, + collateralArbed: 0.257950403803869741 * 1e18, + quoteTokenAmount: 21.163274547333150631 * 1e18, + bondChange: 0.195342779771472726 * 1e18, + isReward: false, + lpAwardTaker: 2_562.597355112798042 * 1e18, + lpAwardKicker: 0 + }); + + _assertLenderLpBalance({ + lender: _taker, + index: _i10016, + lpBalance: 2_562.597355112798042 * 1e18, // arb taker was rewarded LPBs in arbed bucket + depositTime: _startTime + 100 days + 3 hours + }); + _assertLenderLpBalance({ + lender: _lender, + index: _i10016, + lpBalance: 1_000 * 1e18, + depositTime: _startTime + 100 days + 3 hours + }); + _assertKicker({ + kicker: _lender, + claimable: 0, + locked: 0 // kicker was penalized + }); + _assertBucket({ + index: _i10016, + lpBalance: 3_562.597355112798042 * 1e18, // LP balance in arbed bucket increased with LPs awarded for arb taker + collateral: 0.257950403803869741 * 1e18, // arbed collateral added to the arbed bucket + deposit: 978.836725452666849368 * 1e18, // quote token amount is diminished in arbed bucket + exchangeRate: 1 * 1e18 + }); _assertAuction( AuctionParams({ borrower: _borrower, @@ -774,27 +633,30 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { neutralPrice: 0 }) ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 0, + borrowerCollateral: 1.742049596196130259 * 1e18, + borrowert0Np: 0, + borrowerCollateralization: 1 * 1e18 + }); } function testArbTakeReverts() external tearDown { // should revert if borrower not auctioned - _assertArbTakeNoAuction( - { - from: _lender, - borrower: _borrower2, - index: _i9_91 - } - ); + _assertArbTakeNoAuction({ + from: _lender, + borrower: _borrower2, + index: _i9_91 + }); // should revert if auction in grace period - _assertArbTakeAuctionInCooldownRevert( - { - from: _lender, - borrower: _borrower, - index: _i9_91 - } - ); + _assertArbTakeAuctionInCooldownRevert({ + from: _lender, + borrower: _borrower, + index: _i9_91 + }); skip(2.5 hours); @@ -814,49 +676,40 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { neutralPrice: 10.255495938002318100 * 1e18 }) ); - - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 19.778710457642278866 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.115967548076923081 * 1e18, - borrowerCollateralization: 0.983006034276170567 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 19.778710457642278866 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.115967548076923081 * 1e18, + borrowerCollateralization: 0.983006034276170567 * 1e18 + }); // should revert if bucket deposit is 0 - _assertArbTakeAuctionInsufficientLiquidityRevert( - { - from: _taker, - borrower: _borrower, - index: _i100_33 - } - ); + _assertArbTakeAuctionInsufficientLiquidityRevert({ + from: _taker, + borrower: _borrower, + index: _i100_33 + }); // should revert if auction price is greater than the bucket price - _assertArbTakeAuctionPriceGreaterThanBucketPriceRevert( - { - from: _taker, - borrower: _borrower, - index: _i9_91 - } - ); + _assertArbTakeAuctionPriceGreaterThanBucketPriceRevert({ + from: _taker, + borrower: _borrower, + index: _i9_91 + }); skip(4 hours); // 10 borrowers draw debt to enable the min debt check for (uint i=0; i<10; ++i) { - _anonBorrowerDrawsDebt(1_000 * 1e18, 6_000 * 1e18, 7777); + _anonBorrowerDrawsDebt(1_000 * 1e18, 6_000 * 1e18, MAX_FENWICK_INDEX); } // should revert if auction leaves borrower with debt under minimum pool debt - _assertArbTakeDebtUnderMinPoolDebtRevert( - { - from: _taker, - borrower: _borrower, - index: _i9_91 - } - ); + _assertArbTakeDebtUnderMinPoolDebtRevert({ + from: _taker, + borrower: _borrower, + index: _i9_91 + }); } } diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol b/tests/forge/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol index cdbf77375..5a1818a59 100644 --- a/tests/forge/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol @@ -28,75 +28,57 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { _mintCollateralAndApproveTokens(_lender1, 4 * 1e18); // Lender adds Quote token accross 5 prices - _addInitialLiquidity( - { - from: _lender, - amount: 2_000 * 1e18, - index: _i9_91 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 5_000 * 1e18, - index: _i9_81 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 11_000 * 1e18, - index: _i9_72 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 25_000 * 1e18, - index: _i9_62 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 30_000 * 1e18, - index: _i9_52 - } - ); + _addInitialLiquidity({ + from: _lender, + amount: 2_000 * 1e18, + index: _i9_91 + }); + _addInitialLiquidity({ + from: _lender, + amount: 5_000 * 1e18, + index: _i9_81 + }); + _addInitialLiquidity({ + from: _lender, + amount: 11_000 * 1e18, + index: _i9_72 + }); + _addInitialLiquidity({ + from: _lender, + amount: 25_000 * 1e18, + index: _i9_62 + }); + _addInitialLiquidity({ + from: _lender, + amount: 30_000 * 1e18, + index: _i9_52 + }); // first borrower adds collateral token and borrows - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - amount: 2 * 1e18 - } - ); - _borrow( - { - from: _borrower, - amount: 19.25 * 1e18, - indexLimit: _i9_91, - newLup: 9.917184843435912074 * 1e18 - } - ); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + amount: 2 * 1e18 + }); + _borrow({ + from: _borrower, + amount: 19.25 * 1e18, + indexLimit: _i9_91, + newLup: 9.917184843435912074 * 1e18 + }); // second borrower adds collateral token and borrows - _pledgeCollateral( - { - from: _borrower2, - borrower: _borrower2, - amount: 1_000 * 1e18 - } - ); - _borrow( - { - from: _borrower2, - amount: 7_980 * 1e18, - indexLimit: _i9_72, - newLup: 9.721295865031779605 * 1e18 - } - ); + _pledgeCollateral({ + from: _borrower2, + borrower: _borrower2, + amount: 1_000 * 1e18 + }); + _borrow({ + from: _borrower2, + amount: 7_980 * 1e18, + indexLimit: _i9_72, + newLup: 9.721295865031779605 * 1e18 + }); /*****************************/ /*** Assert pre-kick state ***/ @@ -119,43 +101,36 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { interestRateUpdate: _startTime }) ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 19.268509615384615394 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.115967548076923081 * 1e18, - borrowerCollateralization: 1.009034539679184679 * 1e18 - } - ); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 7_987.673076923076926760 * 1e18, - borrowerCollateral: 1_000 * 1e18, - borrowert0Np: 8.471136974495192174 * 1e18, - borrowerCollateralization: 1.217037273735858713 * 1e18 - } - ); - _assertReserveAuction( - { - reserves: 7.691586538461542154 * 1e18, - claimableReserves : 0, - claimableReservesRemaining: 0, - auctionPrice: 0, - timeRemaining: 0 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 19.268509615384615394 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.115967548076923081 * 1e18, + borrowerCollateralization: 1.009034539679184679 * 1e18 + }); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 7_987.673076923076926760 * 1e18, + borrowerCollateral: 1_000 * 1e18, + borrowert0Np: 8.471136974495192174 * 1e18, + borrowerCollateralization: 1.217037273735858713 * 1e18 + }); + _assertReserveAuction({ + reserves: 7.691586538461542154 * 1e18, + claimableReserves : 0, + claimableReservesRemaining: 0, + auctionPrice: 0, + timeRemaining: 0 + }); + assertEq(_quote.balanceOf(_lender), 47_000 * 1e18); // should revert if there's no auction started - _assertDepositTakeNoAuctionRevert( - { - from: _lender, - borrower: _borrower, - index: _i9_91 - } - ); + _assertDepositTakeNoAuctionRevert({ + from: _lender, + borrower: _borrower, + index: _i9_91 + }); // Skip to make borrower undercollateralized skip(250 days); @@ -176,27 +151,22 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { neutralPrice: 0 }) ); - - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 19.939819504377940339 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.115967548076923081 * 1e18, - borrowerCollateralization: 0.975063576969429891 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 19.939819504377940339 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.115967548076923081 * 1e18, + borrowerCollateralization: 0.975063576969429891 * 1e18 + }); - _kick( - { - from: _lender, - borrower: _borrower, - debt: 20.189067248182664593 * 1e18, - collateral: 2 * 1e18, - bond: 0.199398195043779403 * 1e18, - transferAmount: 0.199398195043779403 * 1e18 - } - ); + _kick({ + from: _lender, + borrower: _borrower, + debt: 20.189067248182664593 * 1e18, + collateral: 2 * 1e18, + bond: 0.199398195043779403 * 1e18, + transferAmount: 0.199398195043779403 * 1e18 + }); _assertAuction( AuctionParams({ @@ -214,195 +184,162 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { neutralPrice: 10.468405239798418677 * 1e18 }) ); - - _assertKicker( - { - kicker: _lender, - claimable: 0, - locked: 0.199398195043779403 * 1e18 - } - ); + _assertKicker({ + kicker: _lender, + claimable: 0, + locked: 0.199398195043779403 * 1e18 + }); } function testDepositTakeCollateralRestrict() external tearDown { skip(6.5 hours); - _assertLenderLpBalance( - { - lender: _taker, - index: _i9_91, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: _i9_91, - lpBalance: 2_000 * 1e27, - depositTime: _startTime - } - ); - _assertBucket( - { - index: _i9_91, - lpBalance: 2_000 * 1e27, - collateral: 0, - deposit: 2_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 20.189741380689676442 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.115967548076923081 * 1e18, - borrowerCollateralization: 0.962993599742653326 * 1e18 - } - ); + _assertLenderLpBalance({ + lender: _taker, + index: _i9_91, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: _lender, + index: _i9_91, + lpBalance: 2_000 * 1e18, + depositTime: _startTime + }); + _assertBucket({ + index: _i9_91, + lpBalance: 2_000 * 1e18, + collateral: 0, + deposit: 2_000 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 20.189741380689676442 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.115967548076923081 * 1e18, + borrowerCollateralization: 0.962993599742653326 * 1e18 + }); // add liquidity to accrue interest and update reserves before deposit take - _addLiquidity( - { - from: _lender1, - amount: 1 * 1e18, - index: _i9_52, - lpAward: 0.999996755983514720095749768 * 1e27, - newLup: 9.721295865031779605 * 1e18 - } - ); - _assertBucket( - { - index: _i9_91, - lpBalance: 2_000 * 1e27, - collateral: 0, - deposit: 2_000.006488054017914000 * 1e18, - exchangeRate: 1.000003244027008957000000000 * 1e27 - } - ); - _assertReserveAuction( - { - reserves: 286.940475866492567343 * 1e18, - claimableReserves : 245.508339417301835201 * 1e18, - claimableReservesRemaining: 0, - auctionPrice: 0, - timeRemaining: 0 - } - ); + _addLiquidity({ + from: _lender1, + amount: 1 * 1e18, + index: _i9_52, + lpAward: 0.999996755983514720 * 1e18, + newLup: 9.721295865031779605 * 1e18 + }); + + _assertBucket({ + index: _i9_91, + lpBalance: 2_000 * 1e18, + collateral: 0, + deposit: 2_000.006488054017914000 * 1e18, + exchangeRate: 1.000003244027008957 * 1e18 + }); + _assertReserveAuction({ + reserves: 286.940475866492567343 * 1e18, + claimableReserves : 245.508339417301835201 * 1e18, + claimableReservesRemaining: 0, + auctionPrice: 0, + timeRemaining: 0 + }); _assertAuction( - AuctionParams({ - borrower: _borrower, - active: true, - kicker: _lender, - bondSize: 0.199398195043779403 * 1e18, - bondFactor: 0.01 * 1e18, - kickTime: block.timestamp - 6.5 hours, - kickMomp: 9.818751856078723036 * 1e18, - totalBondEscrowed: 0.199398195043779403 * 1e18, - auctionPrice: 7.402280333270247968 * 1e18, - debtInAuction: 20.189741380689676442 * 1e18, - thresholdPrice: 10.094870690344838221 * 1e18, - neutralPrice: 10.468405239798418677 * 1e18 - }) - ); - - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 20.189741380689676442 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.115967548076923081 * 1e18, - borrowerCollateralization: 0.962993599742653326 * 1e18 - } + AuctionParams({ + borrower: _borrower, + active: true, + kicker: _lender, + bondSize: 0.199398195043779403 * 1e18, + bondFactor: 0.01 * 1e18, + kickTime: block.timestamp - 6.5 hours, + kickMomp: 9.818751856078723036 * 1e18, + totalBondEscrowed: 0.199398195043779403 * 1e18, + auctionPrice: 7.402280333270247968 * 1e18, + debtInAuction: 20.189741380689676442 * 1e18, + thresholdPrice: 10.094870690344838221 * 1e18, + neutralPrice: 10.468405239798418677 * 1e18 + }) ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 20.189741380689676442 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.115967548076923081 * 1e18, + borrowerCollateralization: 0.962993599742653326 * 1e18 + }); // Amount is restricted by the collateral in the loan - _depositTake( - { - from: _taker, - borrower: _borrower, - kicker: _lender, - index: _i9_91, - collateralArbed: 2 * 1e18, - quoteTokenAmount: 19.834369686871824148 * 1e18, - bondChange: 0.198343696868718241 * 1e18, - isReward: true, - lpAwardTaker: 0, - lpAwardKicker: 0.198343053438495848000000000 * 1e27 - } - ); - - _assertLenderLpBalance( - { - lender: _taker, - index: _i9_91, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: _i9_91, - lpBalance: 2_000.198343053438495848000000000 * 1e27, - depositTime: _startTime + 250 days + 6.5 hours - } - ); - _assertBucket( - { - index: _i9_91, - lpBalance: 2_000.198343053438495848000000000 * 1e27, - collateral: 2 * 1e18, - deposit: 1_980.370462064014808094 * 1e18, - exchangeRate: 1.000003244027008957000258924 * 1e27 - } - ); + _depositTake({ + from: _taker, + borrower: _borrower, + kicker: _lender, + index: _i9_91, + collateralArbed: 2 * 1e18, + quoteTokenAmount: 19.834369686871824148 * 1e18, + bondChange: 0.198343696868718241 * 1e18, + isReward: true, + lpAwardTaker: 0, + lpAwardKicker: 0.198343053438495848 * 1e18 + }); + + _assertLenderLpBalance({ + lender: _taker, + index: _i9_91, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: _lender, + index: _i9_91, + lpBalance: 2_000.198343053438495848 * 1e18, + depositTime: _startTime + 250 days + 6.5 hours + }); + _assertBucket({ + index: _i9_91, + lpBalance: 2_000.198343053438495848 * 1e18, + collateral: 2 * 1e18, + deposit: 1_980.370462064014808094 * 1e18, + exchangeRate: 1.000003244027008957 * 1e18 + }); // reserves should remain the same after deposit take - _assertReserveAuction( - { - reserves: 288.353757763140844694 * 1e18, - claimableReserves : 247.012735034416886695 * 1e18, - claimableReservesRemaining: 0, - auctionPrice: 0, - timeRemaining: 0 - } - ); + _assertReserveAuction({ + reserves: 288.353757763140844694 * 1e18, + claimableReserves : 247.012735034416886695 * 1e18, + claimableReservesRemaining: 0, + auctionPrice: 0, + timeRemaining: 0 + }); _assertAuction( - AuctionParams({ - borrower: _borrower, - active: true, - kicker: _lender, - bondSize: 0.199398195043779403 * 1e18, - bondFactor: 0.01 * 1e18, - kickTime: block.timestamp - 6.5 hours, - kickMomp: 9.818751856078723036 * 1e18, - totalBondEscrowed: 0.199398195043779403 * 1e18, - auctionPrice: 7.402280333270247968 * 1e18, - debtInAuction: 1.966997287334847886 * 1e18, - thresholdPrice: 0, - neutralPrice: 10.468405239798418677 * 1e18 - }) + AuctionParams({ + borrower: _borrower, + active: true, + kicker: _lender, + bondSize: 0.199398195043779403 * 1e18, + bondFactor: 0.01 * 1e18, + kickTime: block.timestamp - 6.5 hours, + kickMomp: 9.818751856078723036 * 1e18, + totalBondEscrowed: 0.199398195043779403 * 1e18, + auctionPrice: 7.402280333270247968 * 1e18, + debtInAuction: 1.966997287334847886 * 1e18, + thresholdPrice: 0, + neutralPrice: 10.468405239798418677 * 1e18 + }) ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 1.966997287334847886 * 1e18, - borrowerCollateral: 0, - borrowert0Np: 10.115967548076923081 * 1e18, - borrowerCollateralization: 0 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 1.966997287334847886 * 1e18, + borrowerCollateral: 0, + borrowert0Np: 10.115967548076923081 * 1e18, + borrowerCollateralization: 0 + }); // deposit take should fail on an auction without any remaining collateral to auction - _assertDepositTakeInsufficentCollateralRevert( - { - from: _taker, - borrower: _borrower, - index: _i9_91 - } - ); + _assertDepositTakeInsufficentCollateralRevert({ + from: _taker, + borrower: _borrower, + index: _i9_91 + }); } function testDepositTakeDebtRestrict() external tearDown { @@ -410,113 +347,92 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { skip(5 hours); _assertAuction( - AuctionParams({ - borrower: _borrower, - active: true, - kicker: _lender, - bondSize: 0.199398195043779403 * 1e18, - bondFactor: 0.01 * 1e18, - kickTime: block.timestamp - 5 hours, - kickMomp: 9.818751856078723036 * 1e18, - totalBondEscrowed: 0.199398195043779403 * 1e18, - auctionPrice: 20.936810479596837344 * 1e18, - debtInAuction: 20.189067248182664592 * 1e18, - thresholdPrice: 10.094792904825850359 * 1e18, - neutralPrice: 10.468405239798418677 * 1e18 - }) - ); - - _addLiquidity( - { - from: _lender, - amount: 25_000 * 1e18, - index: _i1505_26, - lpAward: 25_000 * 1e27, - newLup: 1_505.263728469068226832 * 1e18 - } - ); - - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 20.189585809651700719 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.115967548076923081 * 1e18, - borrowerCollateralization: 149.112888462473727465 * 1e18 - } + AuctionParams({ + borrower: _borrower, + active: true, + kicker: _lender, + bondSize: 0.199398195043779403 * 1e18, + bondFactor: 0.01 * 1e18, + kickTime: block.timestamp - 5 hours, + kickMomp: 9.818751856078723036 * 1e18, + totalBondEscrowed: 0.199398195043779403 * 1e18, + auctionPrice: 20.936810479596837344 * 1e18, + debtInAuction: 20.189067248182664592 * 1e18, + thresholdPrice: 10.094792904825850359 * 1e18, + neutralPrice: 10.468405239798418677 * 1e18 + }) ); - _assertBucket( - { - index: _i1505_26, - lpBalance: 25_000 * 1e27, - collateral: 0.0 * 1e18, - deposit: 25_000 * 1e18, - exchangeRate: 1.0 * 1e27 - } - ); + _addLiquidity({ + from: _lender, + amount: 25_000 * 1e18, + index: _i1505_26, + lpAward: 25_000 * 1e18, + newLup: 1_505.263728469068226832 * 1e18 + }); + + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 20.189585809651700719 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.115967548076923081 * 1e18, + borrowerCollateralization: 149.112888462473727465 * 1e18 + }); + _assertBucket({ + index: _i1505_26, + lpBalance: 25_000 * 1e18, + collateral: 0.0 * 1e18, + deposit: 25_000 * 1e18, + exchangeRate: 1.0 * 1e18 + }); // Amount is restricted by the debt in the loan - _depositTake( - { - from: _taker, - borrower: _borrower, - kicker: _lender, - index: _i1505_26, - collateralArbed: 0.014351542794629452 * 1e18, - quoteTokenAmount: 21.602856816327319769 * 1e18, - bondChange: 0.199398195043779403 * 1e18, - isReward: false, - lpAwardTaker: 0, - lpAwardKicker: 0 - } - ); - - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 0, - borrowerCollateral: 1.985648457205370548 * 1e18, - borrowert0Np: 0, - borrowerCollateralization: 1 * 1e18 - } - ); - - _assertLenderLpBalance( - { - lender: _taker, - index: _i1505_26, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: _i1505_26, - lpBalance: 25_000 * 1e27, - depositTime: block.timestamp - } - ); - _assertBucket( - { - index: _i1505_26, - lpBalance: 25_000 * 1e27, - collateral: 0.014351542794629452 * 1e18, - deposit: 24_978.397143183672680231 * 1e18, - exchangeRate: 1.000000000000000000010323898 * 1e27 - } - ); - - _assertReserveAuction( - { - reserves: 288.543944498908261870 * 1e18, - claimableReserves : 247.213075232011850972 * 1e18, - claimableReservesRemaining: 0, - auctionPrice: 0, - timeRemaining: 0 - } - ); + _depositTake({ + from: _taker, + borrower: _borrower, + kicker: _lender, + index: _i1505_26, + collateralArbed: 0.014351542794629452 * 1e18, + quoteTokenAmount: 21.602856816327319769 * 1e18, + bondChange: 0.199398195043779403 * 1e18, + isReward: false, + lpAwardTaker: 0, + lpAwardKicker: 0 + }); + + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 0, + borrowerCollateral: 1.985648457205370548 * 1e18, + borrowert0Np: 0, + borrowerCollateralization: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _taker, + index: _i1505_26, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: _lender, + index: _i1505_26, + lpBalance: 25_000 * 1e18, + depositTime: block.timestamp + }); + _assertBucket({ + index: _i1505_26, + lpBalance: 25_000 * 1e18, + collateral: 0.014351542794629452 * 1e18, + deposit: 24_978.397143183672680231 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertReserveAuction({ + reserves: 288.543944498908261870 * 1e18, + claimableReserves : 247.213075232011850972 * 1e18, + claimableReservesRemaining: 0, + auctionPrice: 0, + timeRemaining: 0 + }); } function testDepositTakeDepositRestrict() external tearDown { @@ -524,166 +440,127 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { skip(5 hours); _assertAuction( - AuctionParams({ - borrower: _borrower, - active: true, - kicker: _lender, - bondSize: 0.199398195043779403 * 1e18, - bondFactor: 0.01 * 1e18, - kickTime: block.timestamp - 5 hours, - kickMomp: 9.818751856078723036 * 1e18, - totalBondEscrowed: 0.199398195043779403 * 1e18, - auctionPrice: 20.936810479596837344 * 1e18, - debtInAuction: 20.189067248182664592 * 1e18, - thresholdPrice: 10.094792904825850359 * 1e18, - neutralPrice: 10.468405239798418677 * 1e18 - }) + AuctionParams({ + borrower: _borrower, + active: true, + kicker: _lender, + bondSize: 0.199398195043779403 * 1e18, + bondFactor: 0.01 * 1e18, + kickTime: block.timestamp - 5 hours, + kickMomp: 9.818751856078723036 * 1e18, + totalBondEscrowed: 0.199398195043779403 * 1e18, + auctionPrice: 20.936810479596837344 * 1e18, + debtInAuction: 20.189067248182664592 * 1e18, + thresholdPrice: 10.094792904825850359 * 1e18, + neutralPrice: 10.468405239798418677 * 1e18 + }) ); - _addLiquidity( - { - from: _lender, - amount: 15.0 * 1e18, - index: _i1505_26, - lpAward: 15.0 * 1e27, - newLup: 9.721295865031779605 * 1e18 - } - ); + _addLiquidity({ + from: _lender, + amount: 15.0 * 1e18, + index: _i1505_26, + lpAward: 15.0 * 1e18, + newLup: 9.721295865031779605 * 1e18 + }); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 20.189585809651700719 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.115967548076923081 * 1e18, - borrowerCollateralization: 0.963001020098637267 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 20.189585809651700719 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.115967548076923081 * 1e18, + borrowerCollateralization: 0.963001020098637267 * 1e18 + }); // Amount is restricted by the deposit in the bucket in the loan - _depositTake( - { - from: _taker, - borrower: _borrower, - kicker: _lender, - index: _i1505_26, - collateralArbed: 0.009965031187761219 * 1e18, - quoteTokenAmount: 15.0 * 1e18, - bondChange: 0.15 * 1e18, - isReward: false, - lpAwardTaker: 0, - lpAwardKicker: 0 - } - ); + _depositTake({ + from: _taker, + borrower: _borrower, + kicker: _lender, + index: _i1505_26, + collateralArbed: 0.009965031187761219 * 1e18, + quoteTokenAmount: 14.999999999999999995 * 1e18, + bondChange: 0.15 * 1e18, + isReward: false, + lpAwardTaker: 0, + lpAwardKicker: 0 + }); _assertAuction( - AuctionParams({ - borrower: _borrower, - active: false, - kicker: address(0), - bondSize: 0, - bondFactor: 0, - kickTime: 0, - kickMomp: 0, - totalBondEscrowed: 0, - auctionPrice: 0, - debtInAuction: 0, - thresholdPrice: 3.317960196583009903 * 1e18, - neutralPrice: 0 - }) - ); - - _assertBucket( - { - index: _i1505_26, - lpBalance: 15 * 1e27, - collateral: 0.009965031187761219 * 1e18, - deposit: 0, - exchangeRate: 0.999999999999999999688877723 * 1e27 - } - ); - - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 6.602856816327319769 * 1e18, - borrowerCollateral: 1.990034968812238781 * 1e18, - borrowert0Np: 3.417963776167124997 * 1e18, - borrowerCollateralization: 2.929901291475173000 * 1e18 - } - ); - - _assertLenderLpBalance( - { - lender: _taker, - index: _i1505_26, - lpBalance: 0, - depositTime: 0 - } - ); - - _assertLenderLpBalance( - { - lender: _lender, - index: _i1505_26, - lpBalance: 15.0 * 1e27, - depositTime: block.timestamp - } + AuctionParams({ + borrower: _borrower, + active: false, + kicker: address(0), + bondSize: 0, + bondFactor: 0, + kickTime: 0, + kickMomp: 0, + totalBondEscrowed: 0, + auctionPrice: 0, + debtInAuction: 0, + thresholdPrice: 3.317960196583009903 * 1e18, + neutralPrice: 0 + }) ); + _assertBucket({ + index: _i1505_26, + lpBalance: 15 * 1e18, + collateral: 0.009965031187761219 * 1e18, + deposit: 0, + exchangeRate: 1 * 1e18 + }); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 6.602856816327319769 * 1e18, + borrowerCollateral: 1.990034968812238781 * 1e18, + borrowert0Np: 3.384038787324199948 * 1e18, + borrowerCollateralization: 2.929901291475173000 * 1e18 + }); + _assertLenderLpBalance({ + lender: _taker, + index: _i1505_26, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: _lender, + index: _i1505_26, + lpBalance: 15.0 * 1e18, + depositTime: block.timestamp + }); } function testDepositTakeGTNeutralPrice() external tearDown { skip(3 hours); - _addLiquidity( - { - from: _lender, - amount: 1_000 * 1e18, - index: _i10016, - lpAward: 1_000 * 1e27, - newLup: 9.721295865031779605 * 1e18 - } - ); - - _assertLenderLpBalance( - { - lender: _taker, - index: _i10016, - lpBalance: 0, - depositTime: 0 - } - ); - - _assertLenderLpBalance( - { - lender: _lender, - index: _i10016, - lpBalance: 1_000 * 1e27, - depositTime: block.timestamp - } - ); - - _assertBucket( - { - index: _i10016, - lpBalance: 1_000 * 1e27, - collateral: 0, - deposit: 1_000 * 1e18, - exchangeRate: 1.0 * 1e27 - } - ); - - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 20.189378383465778990 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.115967548076923081 * 1e18, - borrowerCollateralization: 0.963010913995558897 * 1e18 - } - ); - + _addLiquidity({ + from: _lender, + amount: 1_000 * 1e18, + index: _i10016, + lpAward: 1_000 * 1e18, + newLup: 9.721295865031779605 * 1e18 + }); + + _assertLenderLpBalance({ + lender: _taker, + index: _i10016, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: _lender, + index: _i10016, + lpBalance: 1_000 * 1e18, + depositTime: block.timestamp + }); + _assertBucket({ + index: _i10016, + lpBalance: 1_000 * 1e18, + collateral: 0, + deposit: 1_000 * 1e18, + exchangeRate: 1.0 * 1e18 + }); _assertAuction( AuctionParams({ borrower: _borrower, @@ -700,74 +577,52 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { neutralPrice: 10.468405239798418677 * 1e18 }) ); - - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 20.189378383465778990 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.115967548076923081 * 1e18, - borrowerCollateralization: 0.963010913995558897 * 1e18 - } - ); - - _depositTake( - { - from: _taker, - borrower: _borrower, - kicker: _lender, - index: _i10016, - collateralArbed: 0.002156704581707556 * 1e18, - quoteTokenAmount: 21.602634870308383519 * 1e18, - bondChange: 0.199398195043779403 * 1e18, - isReward: false, - lpAwardTaker: 0, - lpAwardKicker: 0 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 20.189378383465778990 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.115967548076923081 * 1e18, + borrowerCollateralization: 0.963010913995558897 * 1e18 + }); + + _depositTake({ + from: _taker, + borrower: _borrower, + kicker: _lender, + index: _i10016, + collateralArbed: 0.002156704581707556 * 1e18, + quoteTokenAmount: 21.602634870308383519 * 1e18, + bondChange: 0.199398195043779403 * 1e18, + isReward: false, + lpAwardTaker: 0, + lpAwardKicker: 0 + }); // deposit taker wasn't rewarded any LPBs in arbed bucket - _assertLenderLpBalance( - { - lender: _taker, - index: _i10016, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: _i10016, - lpBalance: 1_000 * 1e27, - depositTime: _startTime + 250 days + 3 hours - } - ); - _assertKicker( - { - kicker: _lender, - claimable: 0, - locked: 0 // kicker was penalized - } - ); - _assertBucket( - { - index: _i10016, - lpBalance: 1_000 * 1e27, // LP balance in arbed bucket increased with LPs awarded for deposit taker - collateral: 0.002156704581707556 * 1e18, // arbed collateral added to the arbed bucket - deposit: 978.397365129691616481* 1e18, // quote token amount is diminished in arbed bucket - exchangeRate: 0.999999999999999999966005802 * 1e27 - } - ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 0, - borrowerCollateral: 1.997843295418292444 * 1e18, - borrowert0Np: 0, - borrowerCollateralization: 1 * 1e18 - } - ); + _assertLenderLpBalance({ + lender: _taker, + index: _i10016, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: _lender, + index: _i10016, + lpBalance: 1_000 * 1e18, + depositTime: _startTime + 250 days + 3 hours + }); + _assertKicker({ + kicker: _lender, + claimable: 0, + locked: 0 // kicker was penalized + }); + _assertBucket({ + index: _i10016, + lpBalance: 1_000 * 1e18, // LP balance in arbed bucket increased with LPs awarded for deposit taker + collateral: 0.002156704581707556 * 1e18, // arbed collateral added to the arbed bucket + deposit: 978.397365129691616481* 1e18, // quote token amount is diminished in arbed bucket + exchangeRate: 1 * 1e18 + }); _assertAuction( AuctionParams({ borrower: _borrower, @@ -784,18 +639,24 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { neutralPrice: 0 }) ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 0, + borrowerCollateral: 1.997843295418292444 * 1e18, + borrowert0Np: 0, + borrowerCollateralization: 1 * 1e18 + }); } function testDepositTakeReverts() external tearDown { // should revert if auction in grace period - _assertDepositTakeAuctionInCooldownRevert( - { - from: _lender, - borrower: _borrower, - index: _i9_91 - } - ); + _assertDepositTakeAuctionInCooldownRevert({ + from: _lender, + borrower: _borrower, + index: _i9_91 + } +); skip(2.5 hours); @@ -817,36 +678,31 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { ); // should revert if bucket deposit is 0 - _assertDepositTakeAuctionInsufficientLiquidityRevert( - { - from: _taker, - borrower: _borrower, - index: _i100_33 - } - ); + _assertDepositTakeAuctionInsufficientLiquidityRevert({ + from: _taker, + borrower: _borrower, + index: _i100_33 + }); // should revert if auction price is greater than the bucket price - _assertDepositTakeAuctionPriceGreaterThanBucketPriceRevert( - { - from: _taker, - borrower: _borrower, - index: _i9_91 - } - ); + _assertDepositTakeAuctionPriceGreaterThanBucketPriceRevert({ + from: _taker, + borrower: _borrower, + index: _i9_91 + }); skip(4 hours); // 10 borrowers draw debt to enable the min debt check - for (uint i=0; i<10; ++i) { - _anonBorrowerDrawsDebt(1_000 * 1e18, 6_000 * 1e18, 7777); - } + for (uint256 i=0; i<10; ++i) { + _anonBorrowerDrawsDebt(1_000 * 1e18, 6_000 * 1e18, MAX_FENWICK_INDEX); + } + // should revert if auction leaves borrower with debt under minimum pool debt - _assertDepositTakeDebtUnderMinPoolDebtRevert( - { - from: _taker, - borrower: _borrower, - index: _i9_91 - } - ); + _assertDepositTakeDebtUnderMinPoolDebtRevert({ + from: _taker, + borrower: _borrower, + index: _i9_91 + }); } } diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsKick.t.sol b/tests/forge/ERC20Pool/ERC20PoolLiquidationsKick.t.sol index 7688a6d21..160c5591f 100644 --- a/tests/forge/ERC20Pool/ERC20PoolLiquidationsKick.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolLiquidationsKick.t.sol @@ -11,12 +11,14 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { address internal _borrower2; address internal _lender; address internal _lender1; + address internal _withdrawRecipient; function setUp() external { - _borrower = makeAddr("borrower"); - _borrower2 = makeAddr("borrower2"); - _lender = makeAddr("lender"); - _lender1 = makeAddr("lender1"); + _borrower = makeAddr("borrower"); + _borrower2 = makeAddr("borrower2"); + _lender = makeAddr("lender"); + _lender1 = makeAddr("lender1"); + _withdrawRecipient = makeAddr("withdrawRecipient"); _mintQuoteAndApproveTokens(_lender, 120_000 * 1e18); _mintQuoteAndApproveTokens(_lender1, 120_000 * 1e18); @@ -26,75 +28,57 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { _mintCollateralAndApproveTokens(_lender1, 4 * 1e18); // Lender adds Quote token accross 5 prices - _addInitialLiquidity( - { - from: _lender, - amount: 2_000 * 1e18, - index: _i9_91 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 5_000 * 1e18, - index: _i9_81 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 11_000 * 1e18, - index: _i9_72 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 25_000 * 1e18, - index: _i9_62 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 30_000 * 1e18, - index: _i9_52 - } - ); + _addInitialLiquidity({ + from: _lender, + amount: 2_000 * 1e18, + index: _i9_91 + }); + _addInitialLiquidity({ + from: _lender, + amount: 5_000 * 1e18, + index: _i9_81 + }); + _addInitialLiquidity({ + from: _lender, + amount: 11_000 * 1e18, + index: _i9_72 + }); + _addInitialLiquidity({ + from: _lender, + amount: 25_000 * 1e18, + index: _i9_62 + }); + _addInitialLiquidity({ + from: _lender, + amount: 30_000 * 1e18, + index: _i9_52 + }); // first borrower adds collateral token and borrows - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - amount: 2 * 1e18 - } - ); - _borrow( - { - from: _borrower, - amount: 19.25 * 1e18, - indexLimit: _i9_91, - newLup: 9.917184843435912074 * 1e18 - } - ); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + amount: 2 * 1e18 + }); + _borrow({ + from: _borrower, + amount: 19.25 * 1e18, + indexLimit: _i9_91, + newLup: 9.917184843435912074 * 1e18 + }); // second borrower adds collateral token and borrows - _pledgeCollateral( - { - from: _borrower2, - borrower: _borrower2, - amount: 1_000 * 1e18 - } - ); - _borrow( - { - from: _borrower2, - amount: 7_980 * 1e18, - indexLimit: _i9_72, - newLup: 9.721295865031779605 * 1e18 - } - ); + _pledgeCollateral({ + from: _borrower2, + borrower: _borrower2, + amount: 1_000 * 1e18 + }); + _borrow({ + from: _borrower2, + amount: 7_980 * 1e18, + indexLimit: _i9_72, + newLup: 9.721295865031779605 * 1e18 + }); /*****************************/ /*** Assert pre-kick state ***/ @@ -117,33 +101,28 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { interestRateUpdate: _startTime }) ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 19.268509615384615394 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.115967548076923081 * 1e18, - borrowerCollateralization: 1.009034539679184679 * 1e18 - } - ); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 7_987.673076923076926760 * 1e18, - borrowerCollateral: 1_000 * 1e18, - borrowert0Np: 8.471136974495192174 * 1e18, - borrowerCollateralization: 1.217037273735858713 * 1e18 - } - ); - _assertReserveAuction( - { - reserves: 7.691586538461542154 * 1e18, - claimableReserves : 0, - claimableReservesRemaining: 0, - auctionPrice: 0, - timeRemaining: 0 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 19.268509615384615394 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.115967548076923081 * 1e18, + borrowerCollateralization: 1.009034539679184679 * 1e18 + }); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 7_987.673076923076926760 * 1e18, + borrowerCollateral: 1_000 * 1e18, + borrowert0Np: 8.471136974495192174 * 1e18, + borrowerCollateralization: 1.217037273735858713 * 1e18 + }); + _assertReserveAuction({ + reserves: 7.691586538461542154 * 1e18, + claimableReserves : 0, + claimableReservesRemaining: 0, + auctionPrice: 0, + timeRemaining: 0 + }); + assertEq(_quote.balanceOf(_lender), 47_000 * 1e18); } @@ -168,27 +147,22 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { neutralPrice: 0 }) ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 19.534277977147272573 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.115967548076923081 * 1e18, + borrowerCollateralization: 0.995306391810796636 * 1e18 + }); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 19.534277977147272573 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.115967548076923081 * 1e18, - borrowerCollateralization: 0.995306391810796636 * 1e18 - } - ); - - _kick( - { - from: _lender, - borrower: _borrower, - debt: 19.778456451861613480 * 1e18, - collateral: 2 * 1e18, - bond: 0.195342779771472726 * 1e18, - transferAmount: 0.195342779771472726 * 1e18 - } - ); + _kick({ + from: _lender, + borrower: _borrower, + debt: 19.778456451861613480 * 1e18, + collateral: 2 * 1e18, + bond: 0.195342779771472726 * 1e18, + transferAmount: 0.195342779771472726 * 1e18 + }); /******************************/ /*** Assert Post-kick state ***/ @@ -211,25 +185,23 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { interestRateUpdate: block.timestamp }) ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 19.778456451861613480 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.115967548076923081 * 1e18, - borrowerCollateralization: 0.983018658578564579 * 1e18 - } - ); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 8_097.846143253778448241 * 1e18, - borrowerCollateral: 1_000 * 1e18, - borrowert0Np: 8.471136974495192174 * 1e18, - borrowerCollateralization: 1.200479200648987171 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 19.778456451861613480 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.115967548076923081 * 1e18, + borrowerCollateralization: 0.983018658578564579 * 1e18 + }); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 8_097.846143253778448241 * 1e18, + borrowerCollateral: 1_000 * 1e18, + borrowert0Np: 8.471136974495192174 * 1e18, + borrowerCollateralization: 1.200479200648987171 * 1e18 + }); + assertEq(_quote.balanceOf(_lender), 46_999.804657220228527274 * 1e18); + _assertAuction( AuctionParams({ borrower: _borrower, @@ -246,55 +218,44 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { neutralPrice: 10.255495938002318100 * 1e18 }) ); - _assertKicker( - { - kicker: _lender, - claimable: 0, - locked: 0.195342779771472726 * 1e18 - } - ); - _assertReserveAuction( - { - reserves: 23.872320013924039720 * 1e18, - claimableReserves : 0, - claimableReservesRemaining: 0, - auctionPrice: 0, - timeRemaining: 0 - } - ); + _assertKicker({ + kicker: _lender, + claimable: 0, + locked: 0.195342779771472726 * 1e18 + }); + _assertReserveAuction({ + reserves: 23.872320013924039720 * 1e18, + claimableReserves : 0, + claimableReservesRemaining: 0, + auctionPrice: 0, + timeRemaining: 0 + }); // kick should fail if borrower properly collateralized - _assertKickCollateralizedBorrowerRevert( - { - from: _lender, - borrower: _borrower2 - } - ); + _assertKickCollateralizedBorrowerRevert({ + from: _lender, + borrower: _borrower2 + }); - _assertDepositLockedByAuctionDebtRevert( - { - operator: _lender, - amount: 100 * 1e18, - index: _i9_91 - } - ); + _assertDepositLockedByAuctionDebtRevert({ + operator: _lender, + amount: 100 * 1e18, + index: _i9_91 + }); - // check locked pool actions if auction kicked for more than 72 hours and auction head not cleared skip(80 hours); - _assertRemoveLiquidityAuctionNotClearedRevert( - { - from: _lender, - amount: 1_000 * 1e18, - index: _i9_91 - } - ); - _assertRemoveCollateralAuctionNotClearedRevert( - { - from: _lender, - amount: 10 * 1e18, - index: _i9_91 - } - ); + + // check locked pool actions if auction kicked for more than 72 hours and auction head not cleared + _assertRemoveLiquidityAuctionNotClearedRevert({ + from: _lender, + amount: 1_000 * 1e18, + index: _i9_91 + }); + _assertRemoveCollateralAuctionNotClearedRevert({ + from: _lender, + amount: 10 * 1e18, + index: _i9_91 + }); } function testKickAndSaveByRepay() external tearDown { @@ -318,27 +279,23 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { neutralPrice: 0 }) ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 19.534277977147272573 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.115967548076923081 * 1e18, + borrowerCollateralization: 0.995306391810796636 * 1e18 + }); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 19.534277977147272573 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.115967548076923081 * 1e18, - borrowerCollateralization: 0.995306391810796636 * 1e18 - } - ); + _kick({ + from: _lender, + borrower: _borrower, + debt: 19.778456451861613480 * 1e18, + collateral: 2 * 1e18, + bond: 0.195342779771472726 * 1e18, + transferAmount: 0.195342779771472726 * 1e18 + }); - _kick( - { - from: _lender, - borrower: _borrower, - debt: 19.778456451861613480 * 1e18, - collateral: 2 * 1e18, - bond: 0.195342779771472726 * 1e18, - transferAmount: 0.195342779771472726 * 1e18 - } - ); _assertAuction( AuctionParams({ borrower: _borrower, @@ -355,25 +312,21 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { neutralPrice: 10.255495938002318100 * 1e18 }) ); + _assertKicker({ + kicker: _lender, + claimable: 0, + locked: 0.195342779771472726 * 1e18 + }); - _assertKicker( - { - kicker: _lender, - claimable: 0, - locked: 0.195342779771472726 * 1e18 - } - ); + _repayAndSettleAuction({ + from: _borrower, + borrower: _borrower, + amount: 2 * 1e18, + repaid: 2 * 1e18, + collateral: 2 * 1e18, + newLup: 9.721295865031779605 * 1e18 + }); - _repayAndSettleAuction( - { - from: _borrower, - borrower: _borrower, - amount: 2 * 1e18, - repaid: 2 * 1e18, - collateral: 2 * 1e18, - newLup: 9.721295865031779605 * 1e18 - } - ); _assertAuction( AuctionParams({ borrower: _borrower, @@ -390,40 +343,35 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { neutralPrice: 0 }) ); - _assertKicker( - { - kicker: _lender, - claimable: 0.195342779771472726 * 1e18, - locked: 0 - } - ); + _assertKicker({ + kicker: _lender, + claimable: 0.195342779771472726 * 1e18, + locked: 0 + }); // Skip to make borrower undercollateralized again skip(750 days); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 19.500754673204780610 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 9.347497433934260033 * 1e18, - borrowerCollateralization: 0.997017400397270737 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 19.500754673204780610 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 9.254718877190426162 * 1e18, + borrowerCollateralization: 0.997017400397270737 * 1e18 + }); // Kick method only emit Kick event and doesn't call transfer method when kicker has enough bond amount in claimable - _kick( - { - from: _lender, - borrower: _borrower, - debt: 19.720138163278334392 * 1e18, - collateral: 2 * 1e18, - bond: 0.195007546732047806 * 1e18, - transferAmount: 0 - } - ); + _kick({ + from: _lender, + borrower: _borrower, + debt: 19.720138163278334392 * 1e18, + collateral: 2 * 1e18, + bond: 0.195007546732047806 * 1e18, + transferAmount: 0 + }); uint256 snapshot = vm.snapshot(); + // kicker not saved if partial debt paid only _repayDebt({ from: _borrower, @@ -444,12 +392,13 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { kickTime: _startTime + 850 days, kickMomp: 9.818751856078723036 * 1e18, totalBondEscrowed: 0.195007546732047806 * 1e18, - auctionPrice: 332.622741621515951584 * 1e18, + auctionPrice: 329.321295632797165376 * 1e18, debtInAuction: 19.720038163278334392 * 1e18, thresholdPrice: 9.860019081639167196 * 1e18, - neutralPrice: 10.394460675672373487 * 1e18 + neutralPrice: 10.291290488524911418 * 1e18 }) ); + vm.revertTo(snapshot); // kicker saved if enough debt paid @@ -479,18 +428,31 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { }) ); - // kicker withdraws his auction bonds + // kicker balance befor withdraw auction bonds + assertEq(_quote.balanceOf(_lender), 46_999.804657220228527274 * 1e18); + + snapshot = vm.snapshot(); + changePrank(_lender); + + // kicker withdraws auction bonds and transfer to a different address + _pool.withdrawBonds(_withdrawRecipient); + + assertEq(_quote.balanceOf(_withdrawRecipient), 0.195342779771472726 * 1e18); assertEq(_quote.balanceOf(_lender), 46_999.804657220228527274 * 1e18); - _pool.withdrawBonds(); + + vm.revertTo(snapshot); + + // kicker withdraws auction bonds + _pool.withdrawBonds(_lender); + assertEq(_quote.balanceOf(_lender), 47_000 * 1e18); - _assertKicker( - { - kicker: _lender, - claimable: 0, - locked: 0 - } - ); + + _assertKicker({ + kicker: _lender, + claimable: 0, + locked: 0 + }); } function testKickAndSaveByPledgeCollateral() external tearDown { @@ -514,27 +476,23 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { neutralPrice: 0 }) ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 19.534277977147272573 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.115967548076923081 * 1e18, + borrowerCollateralization: 0.995306391810796636 * 1e18 + }); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 19.534277977147272573 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.115967548076923081 * 1e18, - borrowerCollateralization: 0.995306391810796636 * 1e18 - } - ); + _kick({ + from: _lender, + borrower: _borrower, + debt: 19.778456451861613480 * 1e18, + collateral: 2 * 1e18, + bond: 0.195342779771472726 * 1e18, + transferAmount: 0.195342779771472726 * 1e18 + }); - _kick( - { - from: _lender, - borrower: _borrower, - debt: 19.778456451861613480 * 1e18, - collateral: 2 * 1e18, - bond: 0.195342779771472726 * 1e18, - transferAmount: 0.195342779771472726 * 1e18 - } - ); _assertAuction( AuctionParams({ borrower: _borrower, @@ -551,22 +509,19 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { neutralPrice: 10.255495938002318100 * 1e18 }) ); - _assertKicker( - { - kicker: _lender, - claimable: 0, - locked: 0.195342779771472726 * 1e18 - } - ); + _assertKicker({ + kicker: _lender, + claimable: 0, + locked: 0.195342779771472726 * 1e18 + }); + + _pledgeCollateralAndSettleAuction({ + from: _borrower, + borrower: _borrower, + amount: 2 * 1e18, + collateral: 4 * 1e18 // collateral after auction settled = 2 new pledged + initial 2 collateral pledged + }); - _pledgeCollateralAndSettleAuction( - { - from: _borrower, - borrower: _borrower, - amount: 2 * 1e18, - collateral: 4 * 1e18 // collateral after auction settled = 2 new pledged + initial 2 collateral pledged - } - ); _assertAuction( AuctionParams({ borrower: _borrower, @@ -583,26 +538,25 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { neutralPrice: 0 }) ); - _assertKicker( - { - kicker: _lender, - claimable: 0.195342779771472726 * 1e18, - locked: 0 - } - ); + _assertKicker({ + kicker: _lender, + claimable: 0.195342779771472726 * 1e18, + locked: 0 + }); // kicker withdraws his auction bonds changePrank(_lender); assertEq(_quote.balanceOf(_lender), 46_999.804657220228527274 * 1e18); - _pool.withdrawBonds(); + + _pool.withdrawBonds(_lender); + assertEq(_quote.balanceOf(_lender), 47_000 * 1e18); - _assertKicker( - { - kicker: _lender, - claimable: 0, - locked: 0 - } - ); + + _assertKicker({ + kicker: _lender, + claimable: 0, + locked: 0 + }); } function testKickActiveAuctionReverts() external tearDown { @@ -626,27 +580,23 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { neutralPrice: 0 }) ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 19.534277977147272573 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.115967548076923081 * 1e18, + borrowerCollateralization: 0.995306391810796636 * 1e18 + }); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 19.534277977147272573 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.115967548076923081 * 1e18, - borrowerCollateralization: 0.995306391810796636 * 1e18 - } - ); + _kick({ + from: _lender, + borrower: _borrower, + debt: 19.778456451861613480 * 1e18, + collateral: 2 * 1e18, + bond: 0.195342779771472726 * 1e18, + transferAmount: 0.195342779771472726 * 1e18 + }); - _kick( - { - from: _lender, - borrower: _borrower, - debt: 19.778456451861613480 * 1e18, - collateral: 2 * 1e18, - bond: 0.195342779771472726 * 1e18, - transferAmount: 0.195342779771472726 * 1e18 - } - ); _assertAuction( AuctionParams({ borrower: _borrower, @@ -665,92 +615,76 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { ); // should not allow borrower to draw more debt if auction kicked - _assertBorrowAuctionActiveRevert( - { - from: _borrower, - amount: 1 * 1e18, - indexLimit: 7000 - } - ); + _assertBorrowAuctionActiveRevert({ + from: _borrower, + amount: 1 * 1e18, + indexLimit: 7000 + }); } function testInterestsAccumulationWithAllLoansAuctioned() external tearDown { // Borrower2 borrows - _borrow( - { - from: _borrower2, - amount: 1_730 * 1e18, - indexLimit: _i9_72, - newLup: 9.721295865031779605 * 1e18 - } - ); + _borrow({ + from: _borrower2, + amount: 1_730 * 1e18, + indexLimit: _i9_72, + newLup: 9.721295865031779605 * 1e18 + }); // Skip to make borrower undercollateralized skip(100 days); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 19.534277977147272573 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.115967548076923081 * 1e18, - borrowerCollateralization: 0.995306391810796636 * 1e18 - } - ); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 9_853.394241979221645666 * 1e18, - borrowerCollateral: 1_000 * 1e18, - borrowert0Np: 10.307611531622595991 * 1e18, - borrowerCollateralization: 0.986593617011217057 * 1e18 - } - ); - _assertLoans( - { - noOfLoans: 2, - maxBorrower: _borrower2, - maxThresholdPrice: 9.719336538461538466 * 1e18 - } - ); + + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 19.534277977147272573 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.115967548076923081 * 1e18, + borrowerCollateralization: 0.995306391810796636 * 1e18 + }); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 9_853.394241979221645666 * 1e18, + borrowerCollateral: 1_000 * 1e18, + borrowert0Np: 10.307611531622595991 * 1e18, + borrowerCollateralization: 0.986593617011217057 * 1e18 + }); + _assertLoans({ + noOfLoans: 2, + maxBorrower: _borrower2, + maxThresholdPrice: 9.719336538461538466 * 1e18 + }); // kick first loan - _kick( - { - from: _lender, - borrower: _borrower2, - debt: 9_976.561670003961916237 * 1e18, - collateral: 1_000 * 1e18, - bond: 98.533942419792216457 * 1e18, - transferAmount: 98.533942419792216457 * 1e18 - } - ); - _assertLoans( - { - noOfLoans: 1, - maxBorrower: _borrower, - maxThresholdPrice: 9.767138988573636287 * 1e18 - } - ); + _kick({ + from: _lender, + borrower: _borrower2, + debt: 9_976.561670003961916237 * 1e18, + collateral: 1_000 * 1e18, + bond: 98.533942419792216457 * 1e18, + transferAmount: 98.533942419792216457 * 1e18 + }); + + _assertLoans({ + noOfLoans: 1, + maxBorrower: _borrower, + maxThresholdPrice: 9.767138988573636287 * 1e18 + }); // kick 2nd loan - _kick( - { - from: _lender, - borrower: _borrower, - debt: 19.754038604390179389 * 1e18, - collateral: 2 * 1e18, - bond: 0.195342779771472726 * 1e18, - transferAmount: 0.195342779771472726 * 1e18 - } - ); - _assertLoans( - { - noOfLoans: 0, - maxBorrower: address(0), - maxThresholdPrice: 0 - } - ); + _kick({ + from: _lender, + borrower: _borrower, + debt: 19.754038604390179389 * 1e18, + collateral: 2 * 1e18, + bond: 0.195342779771472726 * 1e18, + transferAmount: 0.195342779771472726 * 1e18 + }); + _assertLoans({ + noOfLoans: 0, + maxBorrower: address(0), + maxThresholdPrice: 0 + }); _assertPool( PoolParams({ htp: 0, @@ -772,15 +706,13 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { // force pool interest accumulation skip(14 hours); - _addLiquidity( - { - from: _lender1, - amount: 1 * 1e18, - index: _i9_91, - lpAward: 0.943930837199358257319707317 * 1e27, - newLup: 9.721295865031779605 * 1e18 - } - ); + _addLiquidity({ + from: _lender1, + amount: 1 * 1e18, + index: _i9_91, + lpAward: 0.943930837199358257 * 1e18, + newLup: 9.721295865031779605 * 1e18 + }); _assertPool( PoolParams({ diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsKickWithDeposit.t.sol b/tests/forge/ERC20Pool/ERC20PoolLiquidationsKickWithDeposit.t.sol index 7480b89e5..6bcd439fc 100644 --- a/tests/forge/ERC20Pool/ERC20PoolLiquidationsKickWithDeposit.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolLiquidationsKickWithDeposit.t.sol @@ -42,90 +42,72 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { _mintCollateralAndApproveTokens(_borrower5, 1_000 * 1e18); // Lender 1 adds Quote token accross 2 buckets - _addInitialLiquidity( - { - from: _lender1, - amount: 50_000 * 1e18, - index: 2500 - } - ); - _addInitialLiquidity( - { - from: _lender1, - amount: 50_000 * 1e18, - index: 2501 - } - ); - _addInitialLiquidity( - { - from: _lender1, - amount: 1_000 * 1e18, - index: 2502 - } - ); + _addInitialLiquidity({ + from: _lender1, + amount: 50_000 * 1e18, + index: 2500 + }); + _addInitialLiquidity({ + from: _lender1, + amount: 50_000 * 1e18, + index: 2501 + }); + _addInitialLiquidity({ + from: _lender1, + amount: 1_000 * 1e18, + index: 2502 + }); // all 5 borrowers draw debt from pool - _drawDebt( - { - from: _borrower1, - borrower: _borrower1, - amountToBorrow: 20_000 * 1e18, - limitIndex: 5000, - collateralToPledge: 1_000 * 1e18, - newLup: 3_863.654368867279344664 * 1e18 - } - ); - _drawDebt( - { - from: _borrower2, - borrower: _borrower2, - amountToBorrow: 20_000 * 1e18, - limitIndex: 5000, - collateralToPledge: 1_000 * 1e18, - newLup: 3_863.654368867279344664 * 1e18 - } - ); - _drawDebt( - { - from: _borrower3, - borrower: _borrower3, - amountToBorrow: 20_000 * 1e18, - limitIndex: 5000, - collateralToPledge: 1_000 * 1e18, - newLup: 3_844.432207828138682757 * 1e18 - } - ); - _drawDebt( - { - from: _borrower4, - borrower: _borrower4, - amountToBorrow: 20_000 * 1e18, - limitIndex: 5000, - collateralToPledge: 1_000 * 1e18, - newLup: 3_844.432207828138682757 * 1e18 - } - ); - _drawDebt( - { - from: _borrower5, - borrower: _borrower5, - amountToBorrow: 20_000 * 1e18, - limitIndex: 5000, - collateralToPledge: 1_000 * 1e18, - newLup: 3_825.305679430983794766 * 1e18 - } - ); + _drawDebt({ + from: _borrower1, + borrower: _borrower1, + amountToBorrow: 20_000 * 1e18, + limitIndex: 5000, + collateralToPledge: 1_000 * 1e18, + newLup: 3_863.654368867279344664 * 1e18 + }); + _drawDebt({ + from: _borrower2, + borrower: _borrower2, + amountToBorrow: 20_000 * 1e18, + limitIndex: 5000, + collateralToPledge: 1_000 * 1e18, + newLup: 3_863.654368867279344664 * 1e18 + }); + _drawDebt({ + from: _borrower3, + borrower: _borrower3, + amountToBorrow: 20_000 * 1e18, + limitIndex: 5000, + collateralToPledge: 1_000 * 1e18, + newLup: 3_844.432207828138682757 * 1e18 + }); + _drawDebt({ + from: _borrower4, + borrower: _borrower4, + amountToBorrow: 20_000 * 1e18, + limitIndex: 5000, + collateralToPledge: 1_000 * 1e18, + newLup: 3_844.432207828138682757 * 1e18 + }); + _drawDebt({ + from: _borrower5, + borrower: _borrower5, + amountToBorrow: 20_000 * 1e18, + limitIndex: 5000, + collateralToPledge: 1_000 * 1e18, + newLup: 3_825.305679430983794766 * 1e18 + }); // Lender 2 adds Quote token to top bucket - _addLiquidity( - { - from: _lender2, - amount: 10_000 * 1e18, - index: 2500, - lpAward: 10_000 * 1e27, - newLup: 3_844.432207828138682757 * 1e18 - } - ); + _addLiquidity({ + from: _lender2, + amount: 10_000 * 1e18, + index: 2500, + lpAward: 10_000 * 1e18, + newLup: 3_844.432207828138682757 * 1e18 + }); /*****************************/ /*** Assert pre-kick state ***/ @@ -148,6 +130,7 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { interestRateUpdate: _startTime }) ); + // assert balances assertEq(_quote.balanceOf(address(_pool)), 11_000 * 1e18); assertEq(_quote.balanceOf(_lender1), 49_000 * 1e18); @@ -159,13 +142,11 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { assertEq(_quote.balanceOf(_borrower5), 20_000 * 1e18); // assert lender cannot remove desired amount of 15000 quote tokens as LUP moves below HTP - _assertRemoveLiquidityLupBelowHtpRevert( - { - from: _lender1, - amount: 15_000 * 1e18, - index: 2500 - } - ); + _assertRemoveLiquidityLupBelowHtpRevert({ + from: _lender1, + amount: 15_000 * 1e18, + index: 2500 + }); } function testKickWithDepositAmountHigherThanAuctionBond() external tearDown { @@ -176,29 +157,25 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { */ // assert bucket state pre kick with deposit - _assertBucket( - { - index: 2500, - lpBalance: 60_000 * 1e27, - collateral: 0, - deposit: 60_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); + _assertBucket({ + index: 2500, + lpBalance: 60_000 * 1e18, + collateral: 0, + deposit: 60_000 * 1e18, + exchangeRate: 1 * 1e18 + }); - _kickWithDeposit( - { - from: _lender1, - index: 2500, - borrower: _borrower1, - debt: 20_269.471153846153855500 * 1e18, - collateral: 1_000 * 1e18, - bond: 6_005.769230769230772000 * 1e18, - removedFromDeposit: 6_005.769230769230772000 * 1e18, - transferAmount: 0, - lup: 3_844.432207828138682757 * 1e18 - } - ); + _kickWithDeposit({ + from: _lender1, + index: 2500, + borrower: _borrower1, + debt: 20_269.471153846153855500 * 1e18, + collateral: 1_000 * 1e18, + bond: 6_005.769230769230772000 * 1e18, + removedFromDeposit: 6_005.769230769230772000 * 1e18, + transferAmount: 0, + lup: 3_844.432207828138682757 * 1e18 + }); /******************************/ /*** Assert post-kick state ***/ @@ -221,6 +198,7 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { interestRateUpdate: _startTime }) ); + // assert balances - no change, bond was covered from deposit assertEq(_quote.balanceOf(address(_pool)), 11_000 * 1e18); assertEq(_quote.balanceOf(_lender1), 49_000 * 1e18); @@ -230,41 +208,34 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { assertEq(_quote.balanceOf(_borrower3), 20_000 * 1e18); assertEq(_quote.balanceOf(_borrower4), 20_000 * 1e18); assertEq(_quote.balanceOf(_borrower5), 20_000 * 1e18); - // assert lenders LPs in bucket used - _assertLenderLpBalance( - { - lender: _lender1, - index: 2500, - lpBalance: 43_994.230769230769228 * 1e27, // reduced by amount used to cover auction bond - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: _lender2, - index: 2500, - lpBalance: 10_000 * 1e27, - depositTime: _startTime - } - ); + + // assert lenders LPs in bucket used to kick + _assertLenderLpBalance({ + lender: _lender1, + index: 2500, + lpBalance: 43_994.230769230769228 * 1e18, // reduced by amount used to cover auction bond + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: _lender2, + index: 2500, + lpBalance: 10_000 * 1e18, + depositTime: _startTime + }); // assert bucket LPs - _assertBucket( - { - index: 2500, - lpBalance: 53_994.230769230769228 * 1e27, // reduced by amount used to cover auction bond - collateral: 0, - deposit: 53_994.230769230769228000 * 1e18, // reduced by amount used to cover auction bond - exchangeRate: 1 * 1e27 - } - ); + _assertBucket({ + index: 2500, + lpBalance: 53_994.230769230769228 * 1e18, // reduced by amount used to cover auction bond + collateral: 0, + deposit: 53_994.230769230769228000 * 1e18, // reduced by amount used to cover auction bond + exchangeRate: 1 * 1e18 + }); // assert lender1 as a kicker - _assertKicker( - { - kicker: _lender1, - claimable: 0, - locked: 6_005.769230769230772000 * 1e18 - } - ); + _assertKicker({ + kicker: _lender1, + claimable: 0, + locked: 6_005.769230769230772000 * 1e18 + }); // assert kicked auction _assertAuction( AuctionParams({ @@ -292,26 +263,23 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { */ // borrower 1 draws more debt from pool, bond size will increase from 6_005.769230769230772000 in prev scenario to 8_708.365384615384619400 - _drawDebt( - { - from: _borrower1, - borrower: _borrower1, - amountToBorrow: 9_000 * 1e18, - limitIndex: 5000, - collateralToPledge: 0, - newLup: 3_844.432207828138682757 * 1e18 - } - ); + _drawDebt({ + from: _borrower1, + borrower: _borrower1, + amountToBorrow: 9_000 * 1e18, + limitIndex: 5000, + collateralToPledge: 0, + newLup: 3_844.432207828138682757 * 1e18 + }); // Lender 3 adds collateral to top bucket - _addCollateral( - { - from: _lender3, - amount: 1 * 1e18, - index: 2500, - lpAward: 3_863.654368867279344664 * 1e27 // less than bond size - } - ); + _addCollateral({ + from: _lender3, + amount: 1 * 1e18, + index: 2500, + lpAward: 3_863.654368867279344664 * 1e18 // less than bond size + }); + // assert balances assertEq(_quote.balanceOf(address(_pool)), 2_000 * 1e18); assertEq(_quote.balanceOf(_lender1), 49_000 * 1e18); @@ -323,19 +291,17 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { assertEq(_quote.balanceOf(_borrower4), 20_000 * 1e18); assertEq(_quote.balanceOf(_borrower5), 20_000 * 1e18); - _kickWithDeposit( - { - from: _lender3, - index: 2500, - borrower: _borrower1, - debt: 29_390.733173076923090475 * 1e18, - collateral: 1_000 * 1e18, - bond: 8_708.365384615384619400 * 1e18, - removedFromDeposit: 3_863.654368867279344664 * 1e18, - transferAmount: 4_844.711015748105274736 * 1e18, - lup: 99836282890 - } - ); + _kickWithDeposit({ + from: _lender3, + index: 2500, + borrower: _borrower1, + debt: 29_390.733173076923090475 * 1e18, + collateral: 1_000 * 1e18, + bond: 8_708.365384615384619400 * 1e18, + removedFromDeposit: 3_863.654368867279344664 * 1e18, + transferAmount: 4_844.711015748105274736 * 1e18, + lup: 99836282890 + }); /******************************/ /*** Assert post-kick state ***/ @@ -358,6 +324,7 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { interestRateUpdate: _startTime }) ); + // assert balances assertEq(_quote.balanceOf(address(_pool)), 6_844.711015748105274736 * 1e18); // increased with the amount sent to cover bond assertEq(_quote.balanceOf(_lender1), 49_000 * 1e18); @@ -368,41 +335,34 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { assertEq(_quote.balanceOf(_borrower3), 20_000 * 1e18); assertEq(_quote.balanceOf(_borrower4), 20_000 * 1e18); assertEq(_quote.balanceOf(_borrower5), 20_000 * 1e18); + // assert lenders LPs in bucket used - _assertLenderLpBalance( - { - lender: _lender1, - index: 2500, - lpBalance: 50_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: _lender3, - index: 2500, - lpBalance: 0, - depositTime: _startTime - } - ); + _assertLenderLpBalance({ + lender: _lender1, + index: 2500, + lpBalance: 50_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: _lender3, + index: 2500, + lpBalance: 0, + depositTime: _startTime + }); // assert bucket LPs - _assertBucket( - { - index: 2500, - lpBalance: 60_000 * 1e27, - collateral: 1 * 1e18, - deposit: 56_136.345631132720655336 * 1e18, - exchangeRate: 1 * 1e27 - } - ); + _assertBucket({ + index: 2500, + lpBalance: 60_000 * 1e18, + collateral: 1 * 1e18, + deposit: 56_136.345631132720655336 * 1e18, + exchangeRate: 1 * 1e18 + }); // assert lender3 as a kicker - _assertKicker( - { - kicker: _lender3, - claimable: 0, - locked: 8_708.365384615384619400 * 1e18 - } - ); + _assertKicker({ + kicker: _lender3, + claimable: 0, + locked: 8_708.365384615384619400 * 1e18 + }); // assert kicked auction _assertAuction( AuctionParams({ @@ -429,26 +389,23 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { */ // lender 2 adds liquidity in new top bucket 2499 - _addLiquidity( - { - from: _lender2, - amount: 10_000 * 1e18, - index: 2499, - lpAward: 10_000 * 1e27, - newLup: 3_844.432207828138682757 * 1e18 - } - ); + _addLiquidity({ + from: _lender2, + amount: 10_000 * 1e18, + index: 2499, + lpAward: 10_000 * 1e18, + newLup: 3_844.432207828138682757 * 1e18 + }); + // borrower draws more debt consuming entire deposit from bucket 2499 - _drawDebt( - { - from: _borrower1, - borrower: _borrower1, - amountToBorrow: 15_000 * 1e18, - limitIndex: 5000, - collateralToPledge: 0, - newLup: 3_844.432207828138682757 * 1e18 - } - ); + _drawDebt({ + from: _borrower1, + borrower: _borrower1, + amountToBorrow: 15_000 * 1e18, + limitIndex: 5000, + collateralToPledge: 0, + newLup: 3_844.432207828138682757 * 1e18 + }); // assert balances assertEq(_quote.balanceOf(address(_pool)), 6_000 * 1e18); @@ -462,19 +419,17 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { assertEq(_quote.balanceOf(_borrower5), 20_000 * 1e18); // lender 2 kicks using all LPs from bucket 2499 (10_000) and sending additional quote tokens to cover auction bond (510.096153846153851000) - _kickWithDeposit( - { - from: _lender2, - index: 2499, - borrower: _borrower1, - debt: 35_471.574519230769247125 * 1e18, - collateral: 1_000 * 1e18, - bond: 10_510.096153846153851000 * 1e18, - removedFromDeposit: 10_000 * 1e18, - transferAmount: 510.096153846153851000 * 1e18, - lup: 99836282890 - } - ); + _kickWithDeposit({ + from: _lender2, + index: 2499, + borrower: _borrower1, + debt: 35_471.574519230769247125 * 1e18, + collateral: 1_000 * 1e18, + bond: 10_510.096153846153851000 * 1e18, + removedFromDeposit: 10_000 * 1e18, + transferAmount: 510.096153846153851000 * 1e18, + lup: 99836282890 + }); /******************************/ /*** Assert post-kick state ***/ @@ -497,6 +452,7 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { interestRateUpdate: _startTime }) ); + // assert balances assertEq(_quote.balanceOf(address(_pool)), 6_510.096153846153851000 * 1e18); // increased with the amount sent to cover bond assertEq(_quote.balanceOf(_lender1), 49_000 * 1e18); @@ -507,33 +463,28 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { assertEq(_quote.balanceOf(_borrower3), 20_000 * 1e18); assertEq(_quote.balanceOf(_borrower4), 20_000 * 1e18); assertEq(_quote.balanceOf(_borrower5), 20_000 * 1e18); + // assert lenders LPs in bucket used - _assertLenderLpBalance( - { - lender: _lender2, - index: 2499, - lpBalance: 0, - depositTime: _startTime - } - ); + _assertLenderLpBalance({ + lender: _lender2, + index: 2499, + lpBalance: 0, + depositTime: _startTime + }); // assert bucket - LPs and deposit obliterated - _assertBucket( - { - index: 2499, - lpBalance: 0, - collateral: 0, - deposit: 0, - exchangeRate: 1 * 1e27 - } - ); + _assertBucket({ + index: 2499, + lpBalance: 0, + collateral: 0, + deposit: 0, + exchangeRate: 1 * 1e18 + }); // assert lender2 as a kicker - _assertKicker( - { - kicker: _lender2, - claimable: 0, - locked: 10_510.096153846153851000 * 1e18 - } - ); + _assertKicker({ + kicker: _lender2, + claimable: 0, + locked: 10_510.096153846153851000 * 1e18 + }); // assert kicked auction _assertAuction( AuctionParams({ @@ -548,7 +499,7 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { auctionPrice: 123_636.939803752939029248 * 1e18, debtInAuction: 35_471.574519230769247125 * 1e18, thresholdPrice: 35.471574519230769247 * 1e18, - neutralPrice: 37.154109537259614798 * 1e18 + neutralPrice: 36.969263221153845869 * 1e18 }) ); } @@ -561,46 +512,39 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { */ // lender1 adds collateral to bucket to be entitled to higher deposit than available - _addCollateral( - { - from: _lender1, - amount: 10 * 1e18, - index: 2500, - lpAward: 38636.54368867279344664 * 1e27 - } - ); + _addCollateral({ + from: _lender1, + amount: 10 * 1e18, + index: 2500, + lpAward: 38636.54368867279344664 * 1e18 + }); + // assert lender and bucket LP balances pre kick - _assertLenderLpBalance( - { - lender: _lender1, - index: 2500, - lpBalance: 88_636.54368867279344664 * 1e27, - depositTime: _startTime - } - ); - _assertBucket( - { - index: 2500, - lpBalance: 98_636.54368867279344664 * 1e27, - collateral: 10 * 1e18, - deposit: 60_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); + _assertLenderLpBalance({ + lender: _lender1, + index: 2500, + lpBalance: 88_636.54368867279344664 * 1e18, + depositTime: _startTime + }); + _assertBucket({ + index: 2500, + lpBalance: 98_636.54368867279344664 * 1e18, + collateral: 10 * 1e18, + deposit: 60_000 * 1e18, + exchangeRate: 1 * 1e18 + }); - _kickWithDeposit( - { - from: _lender1, - index: 2500, - borrower: _borrower1, - debt: 20_269.471153846153855500 * 1e18, - collateral: 1_000 * 1e18, - bond: 6_005.769230769230772000 * 1e18, - removedFromDeposit: 6_005.769230769230772000 * 1e18, - transferAmount: 0, - lup: 3_844.432207828138682757 * 1e18 - } - ); + _kickWithDeposit({ + from: _lender1, + index: 2500, + borrower: _borrower1, + debt: 20_269.471153846153855500 * 1e18, + collateral: 1_000 * 1e18, + bond: 6_005.769230769230772000 * 1e18, + removedFromDeposit: 6_005.769230769230772000 * 1e18, + transferAmount: 0, + lup: 3_844.432207828138682757 * 1e18 + }); /******************************/ /*** Assert post-kick state ***/ @@ -623,6 +567,7 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { interestRateUpdate: _startTime }) ); + // assert balances - no change, bond was covered from deposit assertEq(_quote.balanceOf(address(_pool)), 11_000 * 1e18); assertEq(_quote.balanceOf(_lender1), 49_000 * 1e18); @@ -632,41 +577,34 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { assertEq(_quote.balanceOf(_borrower3), 20_000 * 1e18); assertEq(_quote.balanceOf(_borrower4), 20_000 * 1e18); assertEq(_quote.balanceOf(_borrower5), 20_000 * 1e18); + // assert lenders LPs in bucket used - _assertLenderLpBalance( - { - lender: _lender1, - index: 2500, - lpBalance: 82_630.77445790356267464 * 1e27, // reduced by amount used to cover auction bond - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: _lender2, - index: 2500, - lpBalance: 10_000 * 1e27, - depositTime: _startTime - } - ); + _assertLenderLpBalance({ + lender: _lender1, + index: 2500, + lpBalance: 82_630.77445790356267464 * 1e18, // reduced by amount used to cover auction bond + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: _lender2, + index: 2500, + lpBalance: 10_000 * 1e18, + depositTime: _startTime + }); // assert bucket LPs - _assertBucket( - { - index: 2500, - lpBalance: 92_630.77445790356267464 * 1e27, // reduced by amount used to cover auction bond - collateral: 10 * 1e18, - deposit: 53_994.230769230769228000 * 1e18, // reduced by amount used to cover auction bond - exchangeRate: 1 * 1e27 - } - ); + _assertBucket({ + index: 2500, + lpBalance: 92_630.77445790356267464 * 1e18, // reduced by amount used to cover auction bond + collateral: 10 * 1e18, + deposit: 53_994.230769230769228000 * 1e18, // reduced by amount used to cover auction bond + exchangeRate: 1 * 1e18 + }); // assert lender1 as a kicker - _assertKicker( - { - kicker: _lender1, - claimable: 0, - locked: 6_005.769230769230772000 * 1e18 - } - ); + _assertKicker({ + kicker: _lender1, + claimable: 0, + locked: 6_005.769230769230772000 * 1e18 + }); // assert kicked auction _assertAuction( AuctionParams({ @@ -708,19 +646,18 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { assertEq(thresholdPrice, 20.019230769230769240 * 1e18); // kick borrower 1 - _kickWithDeposit( - { - from: _lender1, - index: 2500, - borrower: _borrower1, - debt: 20_269.471153846153855500 * 1e18, - collateral: 1_000 * 1e18, - bond: 6_005.769230769230772000 * 1e18, - removedFromDeposit: 6_005.769230769230772000 * 1e18, - transferAmount: 0, - lup: 3_844.432207828138682757 * 1e18 - } - ); + _kickWithDeposit({ + from: _lender1, + index: 2500, + borrower: _borrower1, + debt: 20_269.471153846153855500 * 1e18, + collateral: 1_000 * 1e18, + bond: 6_005.769230769230772000 * 1e18, + removedFromDeposit: 6_005.769230769230772000 * 1e18, + transferAmount: 0, + lup: 3_844.432207828138682757 * 1e18 + }); + (borrower, thresholdPrice) = _pool.loanInfo(1); assertEq(borrower, _borrower5); assertEq(thresholdPrice, 20.019230769230769240 * 1e18); @@ -748,19 +685,18 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { assertEq(prev, address(0)); // kick borrower 5 - _kickWithDeposit( - { - from: _lender1, - index: 2500, - borrower: _borrower5, - debt: 20_269.471153846153855500 * 1e18, - collateral: 1_000 * 1e18, - bond: 6_005.769230769230772000 * 1e18, - removedFromDeposit: 6_005.769230769230772000 * 1e18, - transferAmount: 0, - lup: 99836282890 - } - ); + _kickWithDeposit({ + from: _lender1, + index: 2500, + borrower: _borrower5, + debt: 20_269.471153846153855500 * 1e18, + collateral: 1_000 * 1e18, + bond: 6_005.769230769230772000 * 1e18, + removedFromDeposit: 6_005.769230769230772000 * 1e18, + transferAmount: 0, + lup: 99836282890 + }); + (borrower, thresholdPrice) = _pool.loanInfo(1); assertEq(borrower, _borrower4); assertEq(thresholdPrice, 20.019230769230769240 * 1e18); @@ -787,19 +723,18 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { assertEq(prev, _borrower1); // kick borrower 4 - _kickWithDeposit( - { - from: _lender1, - index: 2500, - borrower: _borrower4, - debt: 20_269.471153846153855500 * 1e18, - collateral: 1_000 * 1e18, - bond: 6_005.769230769230772000 * 1e18, - removedFromDeposit: 6_005.769230769230772000 * 1e18, - transferAmount: 0, - lup: 99836282890 - } - ); + _kickWithDeposit({ + from: _lender1, + index: 2500, + borrower: _borrower4, + debt: 20_269.471153846153855500 * 1e18, + collateral: 1_000 * 1e18, + bond: 6_005.769230769230772000 * 1e18, + removedFromDeposit: 6_005.769230769230772000 * 1e18, + transferAmount: 0, + lup: 99836282890 + }); + (borrower, thresholdPrice) = _pool.loanInfo(1); assertEq(borrower, _borrower3); assertEq(thresholdPrice, 20.019230769230769240 * 1e18); @@ -830,19 +765,18 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { assertEq(prev, _borrower5); // kick borrower 3 - _kickWithDeposit( - { - from: _lender1, - index: 2500, - borrower: _borrower3, - debt: 20_269.471153846153855500 * 1e18, - collateral: 1_000 * 1e18, - bond: 6_005.769230769230772000 * 1e18, - removedFromDeposit: 6_005.769230769230772000 * 1e18, - transferAmount: 0, - lup: 99836282890 - } - ); + _kickWithDeposit({ + from: _lender1, + index: 2500, + borrower: _borrower3, + debt: 20_269.471153846153855500 * 1e18, + collateral: 1_000 * 1e18, + bond: 6_005.769230769230772000 * 1e18, + removedFromDeposit: 6_005.769230769230772000 * 1e18, + transferAmount: 0, + lup: 99836282890 + }); + (borrower, thresholdPrice) = _pool.loanInfo(1); assertEq(borrower, _borrower2); assertEq(thresholdPrice, 20.019230769230769240 * 1e18); @@ -877,19 +811,18 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { assertEq(prev, _borrower4); // kick borrower 2 - _kickWithDeposit( - { - from: _lender1, - index: 2500, - borrower: _borrower2, - debt: 20_269.471153846153855500 * 1e18, - collateral: 1_000 * 1e18, - bond: 6_005.769230769230772000 * 1e18, - removedFromDeposit: 6_005.769230769230772000 * 1e18, - transferAmount: 0, - lup: 99836282890 - } - ); + _kickWithDeposit({ + from: _lender1, + index: 2500, + borrower: _borrower2, + debt: 20_269.471153846153855500 * 1e18, + collateral: 1_000 * 1e18, + bond: 6_005.769230769230772000 * 1e18, + removedFromDeposit: 6_005.769230769230772000 * 1e18, + transferAmount: 0, + lup: 99836282890 + }); + (borrower, thresholdPrice) = _pool.loanInfo(1); assertEq(borrower, address(0)); assertEq(thresholdPrice, 0); @@ -948,6 +881,7 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { // skip to make loans clearable skip(80 hours); + // settle borrower 2 _assertAuction( AuctionParams({ @@ -965,14 +899,14 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { neutralPrice: 21.020192307692307702 * 1e18 }) ); - _settle( - { - from: _lender1, - borrower: _borrower2, - maxDepth: 1, - settledDebt: 20_269.471153846153855500 * 1e18 - } - ); + + _settle({ + from: _lender1, + borrower: _borrower2, + maxDepth: 1, + settledDebt: 20_269.471153846153855500 * 1e18 + }); + _assertAuction( AuctionParams({ borrower: _borrower2, @@ -989,6 +923,7 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { neutralPrice: 0 }) ); + (, , , , , , head, next, prev) = _pool.auctionInfo(_borrower1); assertEq(head, _borrower1); assertEq(next, _borrower5); @@ -1027,14 +962,14 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { neutralPrice: 21.125293269230769068 * 1e18 }) ); - _settle( - { - from: _lender1, - borrower: _borrower4, - maxDepth: 5, - settledDebt: 20_269.471153846153855500 * 1e18 - } - ); + + _settle({ + from: _lender1, + borrower: _borrower4, + maxDepth: 5, + settledDebt: 20_269.471153846153855500 * 1e18 + }); + _assertAuction( AuctionParams({ borrower: _borrower4, @@ -1051,6 +986,7 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { neutralPrice: 0 }) ); + (, , , , , , head, next, prev) = _pool.auctionInfo(_borrower1); assertEq(head, _borrower1); assertEq(next, _borrower5); @@ -1089,14 +1025,14 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { neutralPrice: 21.020192307692307702 * 1e18 }) ); - _settle( - { - from: _lender1, - borrower: _borrower1, - maxDepth: 5, - settledDebt: 20_269.471153846153855500 * 1e18 - } - ); + + _settle({ + from: _lender1, + borrower: _borrower1, + maxDepth: 5, + settledDebt: 20_269.471153846153855500 * 1e18 + }); + _assertAuction( AuctionParams({ borrower: _borrower1, @@ -1113,6 +1049,7 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { neutralPrice: 0 }) ); + (, , , , , , head, next, prev) = _pool.auctionInfo(_borrower1); assertEq(head, _borrower5); assertEq(next, address(0)); @@ -1151,14 +1088,14 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { neutralPrice: 21.230919735576922742 * 1e18 }) ); - _settle( - { - from: _lender1, - borrower: _borrower5, - maxDepth: 5, - settledDebt: 20_269.471153846153855500 * 1e18 - } - ); + + _settle({ + from: _lender1, + borrower: _borrower5, + maxDepth: 5, + settledDebt: 20_269.471153846153855500 * 1e18 + }); + _assertAuction( AuctionParams({ borrower: _borrower5, @@ -1175,6 +1112,7 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { neutralPrice: 0 }) ); + (, , , , , , head, next, prev) = _pool.auctionInfo(_borrower1); assertEq(head, _borrower3); assertEq(next, address(0)); @@ -1213,14 +1151,14 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { neutralPrice: 21.125293269230769068 * 1e18 }) ); - _settle( - { - from: _lender1, - borrower: _borrower3, - maxDepth: 5, - settledDebt: 20_269.471153846153855500 * 1e18 - } - ); + + _settle({ + from: _lender1, + borrower: _borrower3, + maxDepth: 5, + settledDebt: 20_269.471153846153855500 * 1e18 + }); + _assertAuction( AuctionParams({ borrower: _borrower3, @@ -1237,6 +1175,7 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { neutralPrice: 0 }) ); + (, , , , , , head, next, prev) = _pool.auctionInfo(_borrower1); assertEq(head, address(0)); assertEq(next, address(0)); @@ -1277,124 +1216,102 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { }) ); // assert lender1 after settle - _assertLenderLpBalance( - { - lender: _lender1, - index: 2500, - lpBalance: 19_971.15384615384614 * 1e27, - depositTime: _startTime - } - ); + _assertLenderLpBalance({ + lender: _lender1, + index: 2500, + lpBalance: 19_971.15384615384614 * 1e18, + depositTime: _startTime + }); // assert lender1 as a kicker - _assertKicker( - { - kicker: _lender1, - claimable: 30_028.846153846153860000 * 1e18, - locked: 0 - } - ); + _assertKicker({ + kicker: _lender1, + claimable: 30_028.846153846153860000 * 1e18, + locked: 0 + }); // assert borrowers after settle - _assertBorrower( - { - borrower: _borrower1, - borrowerDebt: 0, - borrowerCollateral: 994.725169378126588636 * 1e18, - borrowert0Np: 21.020192307692307702 * 1e18, - borrowerCollateralization: 1 * 1e18 - } - ); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 0, - borrowerCollateral: 994.751412316543869246 * 1e18, - borrowert0Np: 21.020192307692307702 * 1e18, - borrowerCollateralization: 1 * 1e18 - } - ); - _assertBorrower( - { - borrower: _borrower3, - borrowerDebt: 0, - borrowerCollateral: 0, // last borrower settled - borrowert0Np: 21.125293269230769068 * 1e18, - borrowerCollateralization: 1 * 1e18 - } - ); - _assertBorrower( - { - borrower: _borrower4, - borrowerDebt: 0, - borrowerCollateral: 994.737734630435747061 * 1e18, - borrowert0Np: 21.125293269230769068 * 1e18, - borrowerCollateralization: 1 * 1e18 - } - ); - _assertBorrower( - { - borrower: _borrower5, - borrowerDebt: 0, - borrowerCollateral: 0, - borrowert0Np: 21.230919735576922742 * 1e18, - borrowerCollateralization: 1 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower1, + borrowerDebt: 0, + borrowerCollateral: 994.725169378126588636 * 1e18, + borrowert0Np: 21.020192307692307702 * 1e18, + borrowerCollateralization: 1 * 1e18 + }); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 0, + borrowerCollateral: 994.751412316543869246 * 1e18, + borrowert0Np: 21.020192307692307702 * 1e18, + borrowerCollateralization: 1 * 1e18 + }); + _assertBorrower({ + borrower: _borrower3, + borrowerDebt: 0, + borrowerCollateral: 0, // last borrower settled + borrowert0Np: 21.125293269230769068 * 1e18, + borrowerCollateralization: 1 * 1e18 + }); + _assertBorrower({ + borrower: _borrower4, + borrowerDebt: 0, + borrowerCollateral: 994.737734630435747061 * 1e18, + borrowert0Np: 21.125293269230769068 * 1e18, + borrowerCollateralization: 1 * 1e18 + }); + _assertBorrower({ + borrower: _borrower5, + borrowerDebt: 0, + borrowerCollateral: 0, + borrowert0Np: 21.230919735576922742 * 1e18, + borrowerCollateralization: 1 * 1e18 + }); } function testKickWithDepositReverts() external tearDown { // assert lender cannot kick with a bucket without deposit - _assertKickWithInsufficientLiquidityRevert( - { - from: _lender1, - index: 2_503 - } - ); + _assertKickWithInsufficientLiquidityRevert({ + from: _lender1, + index: 2_503 + }); + // assert lender cannot kick a loan if the proposed LUP doesn't render borrower uncollateralized - _assertKickWithBadProposedLupRevert( - { - from: _lender2, - index: 2_500 - } - ); + _assertKickWithBadProposedLupRevert({ + from: _lender2, + index: 2_500 + }); // Lender 2 adds Quote token at a much lower price the tries to kick with deposit - _addLiquidity( - { - from: _lender3, - amount: 150_000 * 1e18, - index: 7000, - lpAward: 150_000 * 1e27, - newLup: 3_844.432207828138682757 * 1e18 - } - ); - _assertKickPriceBelowProposedLupRevert( - { - from: _lender3, - index: 7000 - } - ); + _addLiquidity({ + from: _lender3, + amount: 150_000 * 1e18, + index: 7000, + lpAward: 150_000 * 1e18, + newLup: 3_844.432207828138682757 * 1e18 + }); + + _assertKickPriceBelowProposedLupRevert({ + from: _lender3, + index: 7000 + }); // asert failure when lender has LPs but insufficient quote token balance to post remaining bond - _addLiquidity( - { - from: _lender4, - amount: 5_000 * 1e18, - index: 2499, - lpAward: 5_000 * 1e27, - newLup: 3_844.432207828138682757 * 1e18 - } - ); + _addLiquidity({ + from: _lender4, + amount: 5_000 * 1e18, + index: 2499, + lpAward: 5_000 * 1e18, + newLup: 3_844.432207828138682757 * 1e18 + }); + // borrower draws more debt consuming entire deposit from bucket 2499 - _drawDebt( - { - from: _borrower1, - borrower: _borrower1, - amountToBorrow: 15_000 * 1e18, - limitIndex: 5000, - collateralToPledge: 0, - newLup: 3_825.305679430983794766 * 1e18 - } - ); + _drawDebt({ + from: _borrower1, + borrower: _borrower1, + amountToBorrow: 15_000 * 1e18, + limitIndex: 5000, + collateralToPledge: 0, + newLup: 3_825.305679430983794766 * 1e18 + }); + changePrank(_lender4); vm.expectRevert("ERC20: transfer amount exceeds balance"); _pool.kickWithDeposit(2499); diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsMisc.t.sol b/tests/forge/ERC20Pool/ERC20PoolLiquidationsMisc.t.sol index 540295041..f01816df5 100644 --- a/tests/forge/ERC20Pool/ERC20PoolLiquidationsMisc.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolLiquidationsMisc.t.sol @@ -27,75 +27,57 @@ contract ERC20PoolLiquidationsMiscTest is ERC20HelperContract { _mintCollateralAndApproveTokens(_lender1, 4 * 1e18); // Lender adds Quote token accross 5 prices - _addInitialLiquidity( - { - from: _lender, - amount: 2_000 * 1e18, - index: _i9_91 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 5_000 * 1e18, - index: _i9_81 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 11_000 * 1e18, - index: _i9_72 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 25_000 * 1e18, - index: _i9_62 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 30_000 * 1e18, - index: _i9_52 - } - ); + _addInitialLiquidity({ + from: _lender, + amount: 2_000 * 1e18, + index: _i9_91 + }); + _addInitialLiquidity({ + from: _lender, + amount: 5_000 * 1e18, + index: _i9_81 + }); + _addInitialLiquidity({ + from: _lender, + amount: 11_000 * 1e18, + index: _i9_72 + }); + _addInitialLiquidity({ + from: _lender, + amount: 25_000 * 1e18, + index: _i9_62 + }); + _addInitialLiquidity({ + from: _lender, + amount: 30_000 * 1e18, + index: _i9_52 + }); // first borrower adds collateral token and borrows - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - amount: 2 * 1e18 - } - ); - _borrow( - { - from: _borrower, - amount: 19.25 * 1e18, - indexLimit: _i9_91, - newLup: 9.917184843435912074 * 1e18 - } - ); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + amount: 2 * 1e18 + }); + _borrow({ + from: _borrower, + amount: 19.25 * 1e18, + indexLimit: _i9_91, + newLup: 9.917184843435912074 * 1e18 + }); // second borrower adds collateral token and borrows - _pledgeCollateral( - { - from: _borrower2, - borrower: _borrower2, - amount: 1_000 * 1e18 - } - ); - _borrow( - { - from: _borrower2, - amount: 7_980 * 1e18, - indexLimit: _i9_72, - newLup: 9.721295865031779605 * 1e18 - } - ); + _pledgeCollateral({ + from: _borrower2, + borrower: _borrower2, + amount: 1_000 * 1e18 + }); + _borrow({ + from: _borrower2, + amount: 7_980 * 1e18, + indexLimit: _i9_72, + newLup: 9.721295865031779605 * 1e18 + }); /*****************************/ /*** Assert pre-kick state ***/ @@ -118,43 +100,36 @@ contract ERC20PoolLiquidationsMiscTest is ERC20HelperContract { interestRateUpdate: _startTime }) ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 19.268509615384615394 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.115967548076923081 * 1e18, - borrowerCollateralization: 1.009034539679184679 * 1e18 - } - ); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 7_987.673076923076926760 * 1e18, - borrowerCollateral: 1_000 * 1e18, - borrowert0Np: 8.471136974495192174 * 1e18, - borrowerCollateralization: 1.217037273735858713 * 1e18 - } - ); - _assertReserveAuction( - { - reserves: 7.691586538461542154 * 1e18, - claimableReserves : 0, - claimableReservesRemaining: 0, - auctionPrice: 0, - timeRemaining: 0 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 19.268509615384615394 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.115967548076923081 * 1e18, + borrowerCollateralization: 1.009034539679184679 * 1e18 + }); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 7_987.673076923076926760 * 1e18, + borrowerCollateral: 1_000 * 1e18, + borrowert0Np: 8.471136974495192174 * 1e18, + borrowerCollateralization: 1.217037273735858713 * 1e18 + }); + _assertReserveAuction({ + reserves: 7.691586538461542154 * 1e18, + claimableReserves : 0, + claimableReservesRemaining: 0, + auctionPrice: 0, + timeRemaining: 0 + }); + assertEq(_quote.balanceOf(_lender), 47_000 * 1e18); // should revert if there's no auction started - _assertTakeNoAuctionRevert( - { - from: _lender, - borrower: _borrower, - maxCollateral: 10 * 1e18 - } - ); + _assertTakeNoAuctionRevert({ + from: _lender, + borrower: _borrower, + maxCollateral: 10 * 1e18 + }); } @@ -163,53 +138,41 @@ contract ERC20PoolLiquidationsMiscTest is ERC20HelperContract { skip(25 hours); // Lender attempts to withdraw entire position - _removeLiquidity( - { - from: _lender, - amount: 2_000.00 * 1e18, - index: _i9_91, - newLup: 9.721295865031779605 * 1e18, - lpRedeem: 1_999.891367962935869240000000000 * 1e27 - } - ); - - _removeLiquidity( - { - from: _lender, - amount: 5_000 * 1e18, - index: _i9_81, - newLup: 9.721295865031779605 * 1e18, - lpRedeem: 4_999.728419907339673101000000000 * 1e27 - } - ); - - _removeLiquidity( - { - from: _lender, - amount: 2_992.8 * 1e18, - index: _i9_72, - newLup: 9.721295865031779605 * 1e18, - lpRedeem: 2_992.637443019737234731000000000 * 1e27 - } - ); + _removeLiquidity({ + from: _lender, + amount: 2_000.00 * 1e18, + index: _i9_91, + newLup: 9.721295865031779605 * 1e18, + lpRedeem: 1_999.891367962935869240 * 1e18 + }); + _removeLiquidity({ + from: _lender, + amount: 5_000 * 1e18, + index: _i9_81, + newLup: 9.721295865031779605 * 1e18, + lpRedeem: 4_999.728419907339673101 * 1e18 + }); + _removeLiquidity({ + from: _lender, + amount: 2_992.8 * 1e18, + index: _i9_72, + newLup: 9.721295865031779605 * 1e18, + lpRedeem: 2_992.637443019737234731 * 1e18 + }); // Lender amount to withdraw is restricted by HTP - _assertRemoveAllLiquidityLupBelowHtpRevert( - { - from: _lender, - index: _i9_72 - } - ); + _assertRemoveAllLiquidityLupBelowHtpRevert({ + from: _lender, + index: _i9_72 + }); - _assertBucket( - { - index: _i9_72, - lpBalance: 8_007.362556980262765269000000000 * 1e27, - collateral: 0, - deposit: 8_007.797508658144068000 * 1e18, - exchangeRate: 1.000054318968922187999940710 * 1e27 - } - ); + _assertBucket({ + index: _i9_72, + lpBalance: 8_007.362556980262765269 * 1e18, + collateral: 0, + deposit: 8_007.797508658144068000 * 1e18, + exchangeRate: 1.000054318968922188 * 1e18 + }); skip(16 hours); @@ -229,38 +192,30 @@ contract ERC20PoolLiquidationsMiscTest is ERC20HelperContract { neutralPrice: 0 }) ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 19.272843317722413898 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.115967548076923081 * 1e18, + borrowerCollateralization: 0.998794730435100101 * 1e18 + }); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 19.272843317722413898 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.115967548076923081 * 1e18, - borrowerCollateralization: 0.998794730435100101 * 1e18 - } - ); - - _kick( - { - from: _lender, - borrower: _borrower, - debt: 19.489662805046791054 * 1e18, - collateral: 2 * 1e18, - bond: 0.192728433177224139 * 1e18, - transferAmount: 0.192728433177224139 * 1e18 - } - ); - - _assertBucket( - { - index: _i9_72, - lpBalance: 8_007.362556980262765269000000000 * 1e27, - collateral: 0, - deposit: 8_008.361347558277120605 * 1e18, - exchangeRate: 1.000124734027079076000026734 * 1e27 - } - ); + _kick({ + from: _lender, + borrower: _borrower, + debt: 19.489662805046791054 * 1e18, + collateral: 2 * 1e18, + bond: 0.192728433177224139 * 1e18, + transferAmount: 0.192728433177224139 * 1e18 + }); + _assertBucket({ + index: _i9_72, + lpBalance: 8_007.362556980262765269 * 1e18, + collateral: 0, + deposit: 8_008.361347558277120605 * 1e18, + exchangeRate: 1.000124734027079076 * 1e18 + }); _assertAuction( AuctionParams({ borrower: _borrower, @@ -279,41 +234,27 @@ contract ERC20PoolLiquidationsMiscTest is ERC20HelperContract { ); // lender cannot withdraw - deposit in buckets within liquidation debt from the top-of-book down are frozen - _assertRemoveDepositLockedByAuctionDebtRevert( - { - from: _lender, - amount: 10.0 * 1e18, - index: _i9_72 - } - ); + _assertRemoveDepositLockedByAuctionDebtRevert({ + from: _lender, + amount: 10.0 * 1e18, + index: _i9_72 + }); // lender cannot move funds - _assertMoveDepositLockedByAuctionDebtRevert( - { - from: _lender, - amount: 10.0 * 1e18, - fromIndex: _i9_72, - toIndex: _i9_81 - } - ); + _assertMoveDepositLockedByAuctionDebtRevert({ + from: _lender, + amount: 10.0 * 1e18, + fromIndex: _i9_72, + toIndex: _i9_81 + }); // lender can add / remove liquidity in buckets that are not within liquidation debt changePrank(_lender1); - _pool.addQuoteToken(2_000 * 1e18, 5000); + _pool.addQuoteToken(2_000 * 1e18, 5000, block.timestamp + 1 minutes); _pool.removeQuoteToken(2_000 * 1e18, 5000); skip(3 hours); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 19.489933125874732298 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.115967548076923081 * 1e18, - borrowerCollateralization: 0.987669594447545452 * 1e18 - } - ); - _assertAuction( AuctionParams({ borrower: _borrower, @@ -330,18 +271,23 @@ contract ERC20PoolLiquidationsMiscTest is ERC20HelperContract { neutralPrice: 10.118242741804267296 * 1e18 }) ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 19.489933125874732298 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.115967548076923081 * 1e18, + borrowerCollateralization: 0.987669594447545452 * 1e18 + }); - _take( - { - from: _lender, - borrower: _borrower, - maxCollateral: 2.0 * 1e18, - bondChange: 0.192728433177224139 * 1e18, - givenAmount: 20.854228444685963559 * 1e18, - collateralTaken: 0.257631549479994909 * 1e18, - isReward: false - } - ); + _take({ + from: _lender, + borrower: _borrower, + maxCollateral: 2.0 * 1e18, + bondChange: 0.192728433177224139 * 1e18, + givenAmount: 20.854228444685963559 * 1e18, + collateralTaken: 0.257631549479994909 * 1e18, + isReward: false + }); // Borrower is removed from auction, keeps collateral in system _assertAuction( @@ -360,17 +306,13 @@ contract ERC20PoolLiquidationsMiscTest is ERC20HelperContract { neutralPrice: 0 }) ); - - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 0, - borrowerCollateral: 1.742368450520005091 * 1e18, - borrowert0Np: 0, - borrowerCollateralization: 1 * 1e18 - } - ); - + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 0, + borrowerCollateral: 1.742368450520005091 * 1e18, + borrowert0Np: 0, + borrowerCollateralization: 1 * 1e18 + }); _assertPool( PoolParams({ htp: 7.991488192808991114 * 1e18, @@ -389,65 +331,53 @@ contract ERC20PoolLiquidationsMiscTest is ERC20HelperContract { }) ); - _removeLiquidity( - { - from: _lender, - amount: 8_008.373442262808822463 * 1e18, - index: _i9_72, - newLup: 9.624807173121239337 * 1e18, - lpRedeem: 8_007.362556980262762824000000000 * 1e27 - } - ); + _removeLiquidity({ + from: _lender, + amount: 8_008.373442262808822463 * 1e18, + index: _i9_72, + newLup: 9.624807173121239337 * 1e18, + lpRedeem: 8_007.362556980262762824 * 1e18 + }); - _assertBucket( - { - index: _i9_72, - lpBalance: 0.000000000000002445000000000 * 1e27, - collateral: 0, - deposit: 2445, - exchangeRate: 1 * 1e27 - } - ); + _assertBucket({ + index: _i9_72, + lpBalance: 0.000000000000002445 * 1e18, + collateral: 0, + deposit: 2445, + exchangeRate: 1 * 1e18 + }); - _removeLiquidity( - { - from: _lender, - amount: 25_000.037756489769875000 * 1e18, - index: _i9_62, - newLup: 9.529276179422528643 * 1e18, - lpRedeem: 25_000 * 1e27 - } - ); + _removeLiquidity({ + from: _lender, + amount: 25_000.037756489769875000 * 1e18, + index: _i9_62, + newLup: 9.529276179422528643 * 1e18, + lpRedeem: 25_000 * 1e18 + }); - _assertBucket( - { - index: _i9_62, - lpBalance: 0, - collateral: 0, - deposit: 0, - exchangeRate: 1 * 1e27 - } - ); + _assertBucket({ + index: _i9_62, + lpBalance: 0, + collateral: 0, + deposit: 0, + exchangeRate: 1 * 1e18 + }); - _removeLiquidity( - { - from: _lender, - amount: 22_000 * 1e18, - index: _i9_52, - newLup: 9.529276179422528643 * 1e18, - lpRedeem: 21_999.966774339181882911000000000 * 1e27 - } - ); + _removeLiquidity({ + from: _lender, + amount: 22_000 * 1e18, + index: _i9_52, + newLup: 9.529276179422528643 * 1e18, + lpRedeem: 21_999.966774339181882911 * 1e18 + }); - _assertBucket( - { - index: _i9_52, - lpBalance: 8_000.033225660818117089000000000 * 1e27, - collateral: 0, - deposit: 8_000.045307787723850000 * 1e18, - exchangeRate: 1.000001510259590794999992127 * 1e27 - } - ); + _assertBucket({ + index: _i9_52, + lpBalance: 8_000.033225660818117089 * 1e18, + collateral: 0, + deposit: 8_000.045307787723850000 * 1e18, + exchangeRate: 1.000001510259590795 * 1e18 + }); _assertRemoveAllLiquidityLupBelowHtpRevert({ from: _lender, @@ -473,16 +403,13 @@ contract ERC20PoolLiquidationsMiscTest is ERC20HelperContract { interestRateUpdate: block.timestamp - 28 hours }) ); - - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 7_990.503913730158190391 * 1e18, - borrowerCollateral: 1_000.00 * 1e18, - borrowert0Np: 8.471136974495192174 * 1e18, - borrowerCollateralization: 1.192575121957988603 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 7_990.503913730158190391 * 1e18, + borrowerCollateral: 1_000.00 * 1e18, + borrowert0Np: 8.471136974495192174 * 1e18, + borrowerCollateralization: 1.192575121957988603 * 1e18 + }); // draw debt is called to trigger accrual of pool interest that will push the lup back up IERC20Pool(address(_pool)).drawDebt(_lender, 0, 0, 0); @@ -507,27 +434,23 @@ contract ERC20PoolLiquidationsMiscTest is ERC20HelperContract { skip(117 days); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 8_105.430237884635118282 * 1e18, - borrowerCollateral: 1_000 * 1e18, - borrowert0Np: 8.471136974495192174 * 1e18, - borrowerCollateralization: 0.000000012317209569 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 8_105.430237884635118282 * 1e18, + borrowerCollateral: 1_000 * 1e18, + borrowert0Np: 8.471136974495192174 * 1e18, + borrowerCollateralization: 0.000000012317209569 * 1e18 + }); // kick borrower 2 changePrank(_lender); _pool.kickWithDeposit(_i9_52); - _assertRemoveDepositLockedByAuctionDebtRevert( - { - from: _lender, - amount: 10.0 * 1e18, - index: _i9_52 - } - ); + _assertRemoveDepositLockedByAuctionDebtRevert({ + from: _lender, + amount: 10.0 * 1e18, + index: _i9_52 + }); skip(10 hours); @@ -547,28 +470,23 @@ contract ERC20PoolLiquidationsMiscTest is ERC20HelperContract { neutralPrice: 8.596021534820399875 * 1e18 }) ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 8_196.162962286455648823 * 1e18, + borrowerCollateral: 1_000 * 1e18, + borrowert0Np: 8.471136974495192174 * 1e18, + borrowerCollateralization: 0.000000012180856255 * 1e18 + }); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 8_196.162962286455648823 * 1e18, - borrowerCollateral: 1_000 * 1e18, - borrowert0Np: 8.471136974495192174 * 1e18, - borrowerCollateralization: 0.000000012180856255 * 1e18 - } - ); - - _take( - { - from: _lender, - borrower: _borrower2, - maxCollateral: 1_000.0 * 1e18, - bondChange: 88.990371346118344167 * 1e18, - givenAmount: 595.579761213908032000 * 1e18, - collateralTaken: 1_000 * 1e18, - isReward: true - } - ); + _take({ + from: _lender, + borrower: _borrower2, + maxCollateral: 1_000.0 * 1e18, + bondChange: 88.990371346118344167 * 1e18, + givenAmount: 595.579761213908032000 * 1e18, + collateralTaken: 1_000 * 1e18, + isReward: true + }); _assertPool( PoolParams({ @@ -587,16 +505,13 @@ contract ERC20PoolLiquidationsMiscTest is ERC20HelperContract { interestRateUpdate: block.timestamp - 10 hours }) ); - - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 8_263.304979778717856240 * 1e18, - borrowerCollateral: 0, - borrowert0Np: 8.471136974495192174 * 1e18, - borrowerCollateralization: 0 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 8_263.304979778717856240 * 1e18, + borrowerCollateral: 0, + borrowert0Np: 8.471136974495192174 * 1e18, + borrowerCollateralization: 0 + }); _assertRemoveLiquidityAuctionNotClearedRevert({ from: _lender, @@ -604,14 +519,12 @@ contract ERC20PoolLiquidationsMiscTest is ERC20HelperContract { index: _i9_52 }); - _settle( - { - from: _lender, - borrower: _borrower2, - maxDepth: 10, - settledDebt: 7_468.035011263740962170 * 1e18 - } - ); + _settle({ + from: _lender, + borrower: _borrower2, + maxDepth: 10, + settledDebt: 7_468.035011263740962170 * 1e18 + }); _assertPool( PoolParams({ @@ -630,51 +543,40 @@ contract ERC20PoolLiquidationsMiscTest is ERC20HelperContract { interestRateUpdate: block.timestamp - 10 hours }) ); - - _assertBucket( - { - index: _i9_91, - lpBalance: 0, - collateral: 0, - deposit: 0, - exchangeRate: 1 * 1e27 - } - ); - _assertBucket( - { - index: _i9_81, - lpBalance: 0, - collateral: 0, - deposit: 0, - exchangeRate: 1 * 1e27 - } - ); - _assertBucket( - { - index: _i9_72, - lpBalance: 0, - collateral: 0, - deposit: 0, - exchangeRate: 1 * 1e27 - } - ); - _assertBucket( - { - index: _i9_62, - lpBalance: 0, - collateral: 0, - deposit: 0, - exchangeRate: 1 * 1e27 - } - ); - _assertBucket( - { - index: _i9_52, - lpBalance: 0 * 1e27, - collateral: 0, - deposit: 0, - exchangeRate: 1 * 1e27 - } - ); + _assertBucket({ + index: _i9_91, + lpBalance: 0, + collateral: 0, + deposit: 0, + exchangeRate: 1 * 1e18 + }); + _assertBucket({ + index: _i9_81, + lpBalance: 0, + collateral: 0, + deposit: 0, + exchangeRate: 1 * 1e18 + }); + _assertBucket({ + index: _i9_72, + lpBalance: 0, + collateral: 0, + deposit: 0, + exchangeRate: 1 * 1e18 + }); + _assertBucket({ + index: _i9_62, + lpBalance: 0, + collateral: 0, + deposit: 0, + exchangeRate: 1 * 1e18 + }); + _assertBucket({ + index: _i9_52, + lpBalance: 0, + collateral: 0, + deposit: 0, + exchangeRate: 1 * 1e18 + }); } } diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsScaled.t.sol b/tests/forge/ERC20Pool/ERC20PoolLiquidationsScaled.t.sol index 98fac1dbc..dcf7813db 100644 --- a/tests/forge/ERC20Pool/ERC20PoolLiquidationsScaled.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolLiquidationsScaled.t.sol @@ -73,15 +73,15 @@ contract ERC20PoolLiquidationsScaledTest is ERC20DSTestPlus { // deposit 200k quote token across 4 buckets uint256 lpBalance; for (uint i=0; i<4; ++i) { - _addInitialLiquidity( - { + + _addInitialLiquidity({ from: _lender, amount: 50_000 * 1e18, index: startBucketId + i - } - ); + }); + (lpBalance, ) = _pool.lenderInfo(startBucketId + i, _lender); - assertEq(lpBalance, 50_000 * 1e27); + assertEq(lpBalance, 50_000 * 1e18); } assertEq(_pool.depositSize(), 200_000 * 1e18); } @@ -295,6 +295,7 @@ contract ERC20PoolLiquidationsScaledTest is ERC20DSTestPlus { , , ) = _pool.auctionInfo(_borrower); + assertEq(auctionKicker, kicker); assertGe(auctionBondFactor, 0.01 * 1e18); assertLe(auctionBondFactor, 0.3 * 1e18); diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsSettle.t.sol b/tests/forge/ERC20Pool/ERC20PoolLiquidationsSettle.t.sol index a37269ff1..0cea6fdc9 100644 --- a/tests/forge/ERC20Pool/ERC20PoolLiquidationsSettle.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolLiquidationsSettle.t.sol @@ -26,75 +26,57 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { _mintCollateralAndApproveTokens(_lender1, 4 * 1e18); // Lender adds Quote token accross 5 prices - _addInitialLiquidity( - { - from: _lender, - amount: 2_000 * 1e18, - index: _i9_91 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 5_000 * 1e18, - index: _i9_81 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 11_000 * 1e18, - index: _i9_72 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 25_000 * 1e18, - index: _i9_62 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 30_000 * 1e18, - index: _i9_52 - } - ); + _addInitialLiquidity({ + from: _lender, + amount: 2_000 * 1e18, + index: _i9_91 + }); + _addInitialLiquidity({ + from: _lender, + amount: 5_000 * 1e18, + index: _i9_81 + }); + _addInitialLiquidity({ + from: _lender, + amount: 11_000 * 1e18, + index: _i9_72 + }); + _addInitialLiquidity({ + from: _lender, + amount: 25_000 * 1e18, + index: _i9_62 + }); + _addInitialLiquidity({ + from: _lender, + amount: 30_000 * 1e18, + index: _i9_52 + }); // first borrower adds collateral token and borrows - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - amount: 2 * 1e18 - } - ); - _borrow( - { - from: _borrower, - amount: 19.25 * 1e18, - indexLimit: _i9_91, - newLup: 9.917184843435912074 * 1e18 - } - ); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + amount: 2 * 1e18 + }); + _borrow({ + from: _borrower, + amount: 19.25 * 1e18, + indexLimit: _i9_91, + newLup: 9.917184843435912074 * 1e18 + }); // second borrower adds collateral token and borrows - _pledgeCollateral( - { - from: _borrower2, - borrower: _borrower2, - amount: 1_000 * 1e18 - } - ); - _borrow( - { - from: _borrower2, - amount: 7_980 * 1e18, - indexLimit: _i9_72, - newLup: 9.721295865031779605 * 1e18 - } - ); + _pledgeCollateral({ + from: _borrower2, + borrower: _borrower2, + amount: 1_000 * 1e18 + }); + _borrow({ + from: _borrower2, + amount: 7_980 * 1e18, + indexLimit: _i9_72, + newLup: 9.721295865031779605 * 1e18 + }); /*****************************/ /*** Assert pre-kick state ***/ @@ -117,47 +99,40 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { interestRateUpdate: _startTime }) ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 19.268509615384615394 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.115967548076923081 * 1e18, - borrowerCollateralization: 1.009034539679184679 * 1e18 - } - ); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 7_987.673076923076926760 * 1e18, - borrowerCollateral: 1_000 * 1e18, - borrowert0Np: 8.471136974495192174 * 1e18, - borrowerCollateralization: 1.217037273735858713 * 1e18 - } - ); - _assertReserveAuction( - { - reserves: 7.691586538461542154 * 1e18, - claimableReserves : 0, - claimableReservesRemaining: 0, - auctionPrice: 0, - timeRemaining: 0 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 19.268509615384615394 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.115967548076923081 * 1e18, + borrowerCollateralization: 1.009034539679184679 * 1e18 + }); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 7_987.673076923076926760 * 1e18, + borrowerCollateral: 1_000 * 1e18, + borrowert0Np: 8.471136974495192174 * 1e18, + borrowerCollateralization: 1.217037273735858713 * 1e18 + }); + _assertReserveAuction({ + reserves: 7.691586538461542154 * 1e18, + claimableReserves : 0, + claimableReservesRemaining: 0, + auctionPrice: 0, + timeRemaining: 0 + }); + assertEq(_quote.balanceOf(_lender), 47_000 * 1e18); } function testSettleOnAuctionKicked72HoursAgoAndPartiallyTaken() external tearDown { // Borrower2 borrows - _borrow( - { - from: _borrower2, - amount: 1_730 * 1e18, - indexLimit: _i9_72, - newLup: 9.721295865031779605 * 1e18 - } - ); + _borrow({ + from: _borrower2, + amount: 1_730 * 1e18, + indexLimit: _i9_72, + newLup: 9.721295865031779605 * 1e18 + }); // Skip to make borrower undercollateralized skip(100 days); @@ -178,27 +153,22 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { neutralPrice: 0 }) ); - - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 9_853.394241979221645666 * 1e18, - borrowerCollateral: 1_000 * 1e18, - borrowert0Np: 10.307611531622595991 * 1e18, - borrowerCollateralization: 0.986593617011217057 * 1e18 - } - ); - - _kick( - { - from: _lender, - borrower: _borrower2, - debt: 9_976.561670003961916237 * 1e18, - collateral: 1_000 * 1e18, - bond: 98.533942419792216457 * 1e18, - transferAmount: 98.533942419792216457 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 9_853.394241979221645666 * 1e18, + borrowerCollateral: 1_000 * 1e18, + borrowert0Np: 10.307611531622595991 * 1e18, + borrowerCollateralization: 0.986593617011217057 * 1e18 + }); + + _kick({ + from: _lender, + borrower: _borrower2, + debt: 9_976.561670003961916237 * 1e18, + collateral: 1_000 * 1e18, + bond: 98.533942419792216457 * 1e18, + transferAmount: 98.533942419792216457 * 1e18 + }); _assertAuction( AuctionParams({ @@ -216,51 +186,41 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { neutralPrice: 10.449783245217816340 * 1e18 }) ); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 9_976.561670003961916237 * 1e18, - borrowerCollateral: 1_000 * 1e18, - borrowert0Np: 10.307611531622595991 * 1e18, - borrowerCollateralization: 0.974413448899967463 * 1e18 - } - ); - _assertBucket( - { - index: _i9_91, - lpBalance: 2_000 * 1e27, - collateral: 0, - deposit: 2_118.781595119199960000 * 1e18, - exchangeRate: 1.05939079755959998 * 1e27 - } - ); - _assertBucket( - { - index: _i9_81, - lpBalance: 5_000 * 1e27, - collateral: 0, - deposit: 5_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); - _assertBucket( - { - index: _i9_72, - lpBalance: 11_000 * 1e27, - collateral: 0, - deposit: 11_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); - _assertBucket( - { - index: _i9_62, - lpBalance: 25_000 * 1e27, - collateral: 0, - deposit: 25_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 9_976.561670003961916237 * 1e18, + borrowerCollateral: 1_000 * 1e18, + borrowert0Np: 10.307611531622595991 * 1e18, + borrowerCollateralization: 0.974413448899967463 * 1e18 + }); + _assertBucket({ + index: _i9_91, + lpBalance: 2_000 * 1e18, + collateral: 0, + deposit: 2_118.781595119199960000 * 1e18, + exchangeRate: 1.05939079755959998 * 1e18 + }); + _assertBucket({ + index: _i9_81, + lpBalance: 5_000 * 1e18, + collateral: 0, + deposit: 5_000 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertBucket({ + index: _i9_72, + lpBalance: 11_000 * 1e18, + collateral: 0, + deposit: 11_000 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertBucket({ + index: _i9_62, + lpBalance: 25_000 * 1e18, + collateral: 0, + deposit: 25_000 * 1e18, + exchangeRate: 1 * 1e18 + }); // skip ahead so take can be called on the loan skip(10 hours); @@ -281,29 +241,25 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { neutralPrice: 10.449783245217816340 * 1e18 }) ); - - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 9_977.074177773911990381 * 1e18, - borrowerCollateral: 1_000 * 1e18, - borrowert0Np: 10.307611531622595991 * 1e18, - borrowerCollateralization: 0.974363394700228467 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 9_977.074177773911990381 * 1e18, + borrowerCollateral: 1_000 * 1e18, + borrowert0Np: 10.307611531622595991 * 1e18, + borrowerCollateralization: 0.974363394700228467 * 1e18 + }); // take partial 800 collateral - _take( - { - from: _lender, - borrower: _borrower2, - maxCollateral: 800 * 1e18, - bondChange: 5.224891622608908288 * 1e18, - givenAmount: 522.489162260890828800 * 1e18, - collateralTaken: 800 * 1e18, - isReward: true - } - ); + _take({ + from: _lender, + borrower: _borrower2, + maxCollateral: 800 * 1e18, + bondChange: 5.224891622608908288 * 1e18, + givenAmount: 522.489162260890828800 * 1e18, + collateralTaken: 800 * 1e18, + isReward: true + }); + _assertAuction( AuctionParams({ borrower: _borrower2, @@ -320,83 +276,66 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { neutralPrice: 10.449783245217816340 * 1e18 }) ); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 10_158.205099579803908908 * 1e18, - borrowerCollateral: 200 * 1e18, - borrowert0Np: 10.307611531622595991 * 1e18, - borrowerCollateralization: 0.191397904841159446 * 1e18 - } - ); - _assertBucket( - { - index: _i9_91, - lpBalance: 2_000 * 1e27, - collateral: 0, - deposit: 2_118.911507166546112000 * 1e18, - exchangeRate: 1.059455753583273056000 * 1e27 - } - ); - _assertBucket( - { - index: _i9_81, - lpBalance: 5_000 * 1e27, - collateral: 0, - deposit: 5_000.306572531226000000 * 1e18, - exchangeRate: 1.0000613145062452 * 1e27 - } - ); - _assertBucket( - { - index: _i9_72, - lpBalance: 11_000 * 1e27, - collateral: 0, - deposit: 11_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); - _assertBucket( - { - index: _i9_62, - lpBalance: 25_000 * 1e27, - collateral: 0, - deposit: 25_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 10_158.205099579803908908 * 1e18, + borrowerCollateral: 200 * 1e18, + borrowert0Np: 10.307611531622595991 * 1e18, + borrowerCollateralization: 0.191397904841159446 * 1e18 + }); + _assertBucket({ + index: _i9_91, + lpBalance: 2_000 * 1e18, + collateral: 0, + deposit: 2_118.911507166546112000 * 1e18, + exchangeRate: 1.059455753583273056 * 1e18 + }); + _assertBucket({ + index: _i9_81, + lpBalance: 5_000 * 1e18, + collateral: 0, + deposit: 5_000.306572531226000000 * 1e18, + exchangeRate: 1.0000613145062452 * 1e18 + }); + _assertBucket({ + index: _i9_72, + lpBalance: 11_000 * 1e18, + collateral: 0, + deposit: 11_000 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertBucket({ + index: _i9_62, + lpBalance: 25_000 * 1e18, + collateral: 0, + deposit: 25_000 * 1e18, + exchangeRate: 1 * 1e18 + }); // settle should affect first 3 buckets, reducing deposit and incrementing collateral skip(73 hours); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 10_162.015140830231868753 * 1e18, - borrowerCollateral: 200 * 1e18, - borrowert0Np: 10.307611531622595991 * 1e18, - borrowerCollateralization: 0.191326144082827145 * 1e18 - } - ); - - _assertBucket( - { - index: _i9_91, - lpBalance: 2_000 * 1e27, - collateral: 0, - deposit: 2_118.911507166546112000 * 1e18, - exchangeRate: 1.059455753583273056000 * 1e27 - } - ); - - _settle( - { - from: _lender, - borrower: _borrower2, - maxDepth: 10, - settledDebt: 10_019.485661146575724663 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 10_162.015140830231868753 * 1e18, + borrowerCollateral: 200 * 1e18, + borrowert0Np: 10.307611531622595991 * 1e18, + borrowerCollateralization: 0.191326144082827145 * 1e18 + }); + _assertBucket({ + index: _i9_91, + lpBalance: 2_000 * 1e18, + collateral: 0, + deposit: 2_118.911507166546112000 * 1e18, + exchangeRate: 1.059455753583273056 * 1e18 + }); + + _settle({ + from: _lender, + borrower: _borrower2, + maxDepth: 10, + settledDebt: 10_019.485661146575724663 * 1e18 + }); _assertAuction( AuctionParams({ @@ -414,54 +353,41 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { neutralPrice: 0 }) ); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 0, - borrowerCollateral: 0, - borrowert0Np: 10.307611531622595991 * 1e18, - borrowerCollateralization: 1 * 1e18 - } - ); - _assertBucket( - { - index: _i9_91, - lpBalance: 2_000 * 1e27, - collateral: 200 * 1e18, - deposit: 0, - exchangeRate: 0.9917184843435912074 * 1e27 - } - ); - _assertBucket( - { - index: _i9_81, - lpBalance: 0, - collateral: 0, - deposit: 0, - exchangeRate: 1 * 1e27 - } - ); - - _assertBucket( - { - index: _i9_72, - lpBalance: 11_000 * 1e27, - collateral: 0, - deposit: 8_807.325768325035155556 * 1e18, - exchangeRate: 0.800665978938639559596000000 * 1e27 - } - ); - - _assertBucket( - { - index: _i9_62, - lpBalance: 25_000 * 1e27, - collateral: 0, - deposit: 25_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); - + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 0, + borrowerCollateral: 0, + borrowert0Np: 10.307611531622595991 * 1e18, + borrowerCollateralization: 1 * 1e18 + }); + _assertBucket({ + index: _i9_91, + lpBalance: 2_000 * 1e18, + collateral: 200 * 1e18, + deposit: 0, + exchangeRate: 0.991718484343591207 * 1e18 + }); + _assertBucket({ + index: _i9_81, + lpBalance: 0, + collateral: 0, + deposit: 0, + exchangeRate: 1 * 1e18 + }); + _assertBucket({ + index: _i9_72, + lpBalance: 11_000 * 1e18, + collateral: 0, + deposit: 8_807.325768325035155556 * 1e18, + exchangeRate: 0.800665978938639560 * 1e18 + }); + _assertBucket({ + index: _i9_62, + lpBalance: 25_000 * 1e18, + collateral: 0, + deposit: 25_000 * 1e18, + exchangeRate: 1 * 1e18 + }); _assertPool( PoolParams({ htp: 9.910303333009215085 * 1e18, @@ -483,14 +409,12 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { function testSettleOnAuctionKicked72HoursAgo() external tearDown { // Borrower2 borrows - _borrow( - { - from: _borrower2, - amount: 1_730 * 1e18, - indexLimit: _i9_72, - newLup: 9.721295865031779605 * 1e18 - } - ); + _borrow({ + from: _borrower2, + amount: 1_730 * 1e18, + indexLimit: _i9_72, + newLup: 9.721295865031779605 * 1e18 + }); // Skip to make borrower undercollateralized skip(100 days); @@ -511,27 +435,23 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { neutralPrice: 0 }) ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 9_853.394241979221645666 * 1e18, + borrowerCollateral: 1_000 * 1e18, + borrowert0Np: 10.307611531622595991 * 1e18, + borrowerCollateralization: 0.986593617011217057 * 1e18 + }); + + _kick({ + from: _lender, + borrower: _borrower2, + debt: 9_976.561670003961916237 * 1e18, + collateral: 1_000 * 1e18, + bond: 98.533942419792216457 * 1e18, + transferAmount: 98.533942419792216457 * 1e18 + }); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 9_853.394241979221645666 * 1e18, - borrowerCollateral: 1_000 * 1e18, - borrowert0Np: 10.307611531622595991 * 1e18, - borrowerCollateralization: 0.986593617011217057 * 1e18 - } - ); - - _kick( - { - from: _lender, - borrower: _borrower2, - debt: 9_976.561670003961916237 * 1e18, - collateral: 1_000 * 1e18, - bond: 98.533942419792216457 * 1e18, - transferAmount: 98.533942419792216457 * 1e18 - } - ); _assertAuction( AuctionParams({ borrower: _borrower2, @@ -548,51 +468,41 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { neutralPrice: 10.449783245217816340 * 1e18 }) ); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 9_976.561670003961916237 * 1e18, - borrowerCollateral: 1_000 * 1e18, - borrowert0Np: 10.307611531622595991 * 1e18, - borrowerCollateralization: 0.974413448899967463 * 1e18 - } - ); - _assertBucket( - { - index: _i9_91, - lpBalance: 2_000 * 1e27, - collateral: 0, - deposit: 2_118.781595119199960000 * 1e18, - exchangeRate: 1.05939079755959998 * 1e27 - } - ); - _assertBucket( - { - index: _i9_81, - lpBalance: 5_000 * 1e27, - collateral: 0, - deposit: 5_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); - _assertBucket( - { - index: _i9_72, - lpBalance: 11_000 * 1e27, - collateral: 0, - deposit: 11_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); - _assertBucket( - { - index: _i9_62, - lpBalance: 25_000 * 1e27, - collateral: 0, - deposit: 25_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 9_976.561670003961916237 * 1e18, + borrowerCollateral: 1_000 * 1e18, + borrowert0Np: 10.307611531622595991 * 1e18, + borrowerCollateralization: 0.974413448899967463 * 1e18 + }); + _assertBucket({ + index: _i9_91, + lpBalance: 2_000 * 1e18, + collateral: 0, + deposit: 2_118.781595119199960000 * 1e18, + exchangeRate: 1.05939079755959998 * 1e18 + }); + _assertBucket({ + index: _i9_81, + lpBalance: 5_000 * 1e18, + collateral: 0, + deposit: 5_000 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertBucket({ + index: _i9_72, + lpBalance: 11_000 * 1e18, + collateral: 0, + deposit: 11_000 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertBucket({ + index: _i9_62, + lpBalance: 25_000 * 1e18, + collateral: 0, + deposit: 25_000 * 1e18, + exchangeRate: 1 * 1e18 + }); // settle should work on an kicked auction if 72 hours passed from kick time // settle should affect first 3 buckets, reducing deposit and incrementing collateral @@ -614,25 +524,21 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { neutralPrice: 10.449783245217816340 * 1e18 }) ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 9_980.303582194898667001 * 1e18, + borrowerCollateral: 1_000 * 1e18, + borrowert0Np: 10.307611531622595991 * 1e18, + borrowerCollateralization: 0.974048112361512224 * 1e18 + }); + + _settle({ + from: _lender, + borrower: _borrower2, + maxDepth: 10, + settledDebt: 9_840.828245192307696845 * 1e18 + }); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 9_980.303582194898667001 * 1e18, - borrowerCollateral: 1_000 * 1e18, - borrowert0Np: 10.307611531622595991 * 1e18, - borrowerCollateralization: 0.974048112361512224 * 1e18 - } - ); - - _settle( - { - from: _lender, - borrower: _borrower2, - maxDepth: 10, - settledDebt: 9_840.828245192307696845 * 1e18 - } - ); _assertAuction( AuctionParams({ borrower: _borrower2, @@ -649,85 +555,71 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { neutralPrice: 0 }) ); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 0, - borrowerCollateral: 0, - borrowert0Np: 10.307611531622595991 * 1e18, - borrowerCollateralization: 1 * 1e18 - } - ); - _assertBucket( - { - index: _i9_91, - lpBalance: 2_000 * 1e27, - collateral: 213.743127712733065764 * 1e18, - deposit: 0, - exchangeRate: 1.059865053270651414002083680 * 1e27 - } - ); - _assertBucket( - { - index: _i9_81, - lpBalance: 5_000 * 1e27, - collateral: 509.457659688392150697 * 1e18, - deposit: 0, - exchangeRate: 1.000447668331784572999225097 * 1e27 - } - ); - _assertBucket( - { - index: _i9_72, - lpBalance: 11_000 * 1e27, - collateral: 276.799212598874783539 * 1e18, - deposit: 8_289.734142970131967959 * 1e18, - exchangeRate: 0.998234653077420534042741275 * 1e27 - } - ); - _assertBucket( - { - index: _i9_62, - lpBalance: 25_000 * 1e27, - collateral: 0, - deposit: 25_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 0, + borrowerCollateral: 0, + borrowert0Np: 10.307611531622595991 * 1e18, + borrowerCollateralization: 1 * 1e18 + }); + _assertBucket({ + index: _i9_91, + lpBalance: 2_000 * 1e18, + collateral: 213.743127712733065764 * 1e18, + deposit: 0, + exchangeRate: 1.059865053270651414 * 1e18 + }); + _assertBucket({ + index: _i9_81, + lpBalance: 5_000 * 1e18, + collateral: 509.457659688392150697 * 1e18, + deposit: 0, + exchangeRate: 1.000447668331784573 * 1e18 + }); + _assertBucket({ + index: _i9_72, + lpBalance: 11_000 * 1e18, + collateral: 276.799212598874783539 * 1e18, + deposit: 8_289.734142970131967959 * 1e18, + exchangeRate: 0.998234653077420534 * 1e18 + }); + _assertBucket({ + index: _i9_62, + lpBalance: 25_000 * 1e18, + collateral: 0, + deposit: 25_000 * 1e18, + exchangeRate: 1 * 1e18 + }); } function testSettleAuctionReverts() external tearDown { // Borrower2 borrows - _borrow( - { - from: _borrower2, - amount: 1_730 * 1e18, - indexLimit: _i9_72, - newLup: 9.721295865031779605 * 1e18 - } - ); + _borrow({ + from: _borrower2, + amount: 1_730 * 1e18, + indexLimit: _i9_72, + newLup: 9.721295865031779605 * 1e18 + }); // Skip to make borrower undercollateralized skip(100 days); // settle should revert on a borrower that is not auctioned - _assertSettleOnNotKickedAuctionRevert( - { - from: _lender, - borrower: _borrower2 - } - ); + _assertSettleOnNotKickedAuctionRevert({ + from: _lender, + borrower: _borrower2 + }); uint256 kickTime = _startTime + 100 days; - _kick( - { - from: _lender, - borrower: _borrower2, - debt: 9_976.561670003961916237 * 1e18, - collateral: 1_000 * 1e18, - bond: 98.533942419792216457 * 1e18, - transferAmount: 98.533942419792216457 * 1e18 - } - ); + + _kick({ + from: _lender, + borrower: _borrower2, + debt: 9_976.561670003961916237 * 1e18, + collateral: 1_000 * 1e18, + bond: 98.533942419792216457 * 1e18, + transferAmount: 98.533942419792216457 * 1e18 + }); + _assertAuction( AuctionParams({ borrower: _borrower2, @@ -744,23 +636,20 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { neutralPrice: 10.449783245217816340 * 1e18 }) ); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 9_976.561670003961916237 * 1e18, - borrowerCollateral: 1_000 * 1e18, - borrowert0Np: 10.307611531622595991 * 1e18, - borrowerCollateralization: 0.974413448899967463 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 9_976.561670003961916237 * 1e18, + borrowerCollateral: 1_000 * 1e18, + borrowert0Np: 10.307611531622595991 * 1e18, + borrowerCollateralization: 0.974413448899967463 * 1e18 + }); // settle should revert on an kicked auction but 72 hours not passed (there's still debt to settle and collateral to be auctioned) - _assertSettleOnNotClearableAuctionRevert( - { - from: _lender, - borrower: _borrower2 - } - ); + _assertSettleOnNotClearableAuctionRevert({ + from: _lender, + borrower: _borrower2 + }); + // skip ahead so take can be called on the loan skip(10 hours); @@ -780,218 +669,208 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { neutralPrice: 10.449783245217816340 * 1e18 }) ); - - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 9_977.074177773911990381 * 1e18, - borrowerCollateral: 1_000 * 1e18, - borrowert0Np: 10.307611531622595991 * 1e18, - borrowerCollateralization: 0.974363394700228467 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 9_977.074177773911990381 * 1e18, + borrowerCollateral: 1_000 * 1e18, + borrowert0Np: 10.307611531622595991 * 1e18, + borrowerCollateralization: 0.974363394700228467 * 1e18 + }); // take entire collateral - _take( - { - from: _lender, - borrower: _borrower2, - maxCollateral: 1_000 * 1e18, - bondChange: 6.531114528261135360 * 1e18, - givenAmount: 653.111452826113536000 * 1e18, - collateralTaken: 1_000 * 1e18, - isReward: true - } - ); + _take({ + from: _lender, + borrower: _borrower2, + maxCollateral: 1_000 * 1e18, + bondChange: 6.531114528261135360 * 1e18, + givenAmount: 653.111452826113536000 * 1e18, + collateralTaken: 1_000 * 1e18, + isReward: true + }); // remove quote tokens should fail since auction head is clearable - _assertRemoveLiquidityAuctionNotClearedRevert( - { - from: _lender, - amount: 1_000 * 1e18, - index: _i9_52 - } - ); - _assertRemoveAllLiquidityAuctionNotClearedRevert( - { - from: _lender, - index: _i9_52 - } - ); + _assertRemoveLiquidityAuctionNotClearedRevert({ + from: _lender, + amount: 1_000 * 1e18, + index: _i9_52 + }); + + _assertRemoveAllLiquidityAuctionNotClearedRevert({ + from: _lender, + index: _i9_52 + }); + // remove collateral should fail since auction head is clearable - _assertRemoveCollateralAuctionNotClearedRevert( - { - from: _lender, - amount: 10 * 1e18, - index: _i9_52 - } - ); + _assertRemoveCollateralAuctionNotClearedRevert({ + from: _lender, + amount: 10 * 1e18, + index: _i9_52 + }); // remove all collateral should fail since auction head is clearable - _assertRemoveAllCollateralAuctionNotClearedRevert( - { - from: _lender, - index: _i9_52 - } - ); + _assertRemoveAllCollateralAuctionNotClearedRevert({ + from: _lender, + index: _i9_52 + }); // add liquidity in same block should be possible as debt was not yet settled / bucket is not yet insolvent - _addLiquidity( - { - from: _lender1, - amount: 100 * 1e18, - index: _i9_91, - lpAward: 94.388085261495553046979329248 * 1e27, - newLup: 9.721295865031779605 * 1e18 - } - ); - _assertLenderLpBalance( - { - lender: _lender1, - index: _i9_91, - lpBalance: 94.388085261495553046979329248 * 1e27, - depositTime: _startTime + 100 days + 10 hours - } - ); + _addLiquidity({ + from: _lender1, + amount: 100 * 1e18, + index: _i9_91, + lpAward: 94.388085261495553047 * 1e18, + newLup: 9.721295865031779605 * 1e18 + }); + + _assertLenderLpBalance({ + lender: _lender1, + index: _i9_91, + lpBalance: 94.388085261495553047 * 1e18, + depositTime: _startTime + 100 days + 10 hours + }); + // adding to a different bucket for testing move in same block with bucket bankruptcy - _addLiquidity( - { - from: _lender1, - amount: 100 * 1e18, - index: _i9_52, - lpAward: 100 * 1e27, - newLup: 9.721295865031779605 * 1e18 - } - ); + _addLiquidity({ + from: _lender1, + amount: 100 * 1e18, + index: _i9_52, + lpAward: 100 * 1e18, + newLup: 9.721295865031779605 * 1e18 + }); // settle to make buckets insolvent // settle should work because there is still debt to settle but no collateral left to auction (even if 72 hours didn't pass from kick) - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 10_028.889031920233428707 * 1e18, - borrowerCollateral: 0, - borrowert0Np: 10.307611531622595991 * 1e18, - borrowerCollateralization: 0 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 10_028.889031920233428707 * 1e18, + borrowerCollateral: 0, + borrowert0Np: 10.307611531622595991 * 1e18, + borrowerCollateralization: 0 + }); assertTrue(block.timestamp - kickTime < 72 hours); // assert auction was kicked less than 72 hours ago - _settle( - { - from: _lender, - borrower: _borrower2, - maxDepth: 10, - settledDebt: 9_891.935520844277346922 * 1e18 - } - ); + + _settle({ + from: _lender, + borrower: _borrower2, + maxDepth: 10, + settledDebt: 9_891.935520844277346922 * 1e18 + }); // bucket is insolvent, balances are resetted - _assertBucket( - { - index: _i9_91, - lpBalance: 0, // bucket is bankrupt - collateral: 0, - deposit: 0, - exchangeRate: 1 * 1e27 - } - ); + _assertBucket({ + index: _i9_91, + lpBalance: 0, // bucket is bankrupt + collateral: 0, + deposit: 0, + exchangeRate: 1 * 1e18 + }); // after bucket bankruptcy lenders balance is zero - _assertLenderLpBalance( - { - lender: _lender, - index: _i9_91, - lpBalance: 0, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: _lender1, - index: _i9_91, - lpBalance: 0, - depositTime: _startTime + 100 days + 10 hours - } - ); + _assertLenderLpBalance({ + lender: _lender, + index: _i9_91, + lpBalance: 0, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: _lender1, + index: _i9_91, + lpBalance: 0, + depositTime: _startTime + 100 days + 10 hours + }); + // cannot add liquidity in same block when bucket marked insolvent - _assertAddLiquidityBankruptcyBlockRevert( - { - from: _lender1, - amount: 1_000 * 1e18, - index: _i9_91 - } - ); + _assertAddLiquidityBankruptcyBlockRevert({ + from: _lender1, + amount: 1_000 * 1e18, + index: _i9_91 + }); + // cannot add collateral in same block when bucket marked insolvent - _assertAddCollateralBankruptcyBlockRevert( - { - from: _lender1, - amount: 10 * 1e18, - index: _i9_91 - } - ); + _assertAddCollateralBankruptcyBlockRevert({ + from: _lender1, + amount: 10 * 1e18, + index: _i9_91 + }); + // cannot move LPs in same block when bucket marked insolvent - _assertMoveLiquidityBankruptcyBlockRevert( - { - from: _lender1, - amount: 10 * 1e18, - fromIndex: _i9_52, - toIndex: _i9_91 - } - ); + _assertMoveLiquidityBankruptcyBlockRevert({ + from: _lender1, + amount: 10 * 1e18, + fromIndex: _i9_52, + toIndex: _i9_91 + }); // all operations should work if not in same block skip(1 hours); - _pool.addQuoteToken(100 * 1e18, _i9_91); - _pool.moveQuoteToken(10 * 1e18, _i9_52, _i9_91); - ERC20Pool(address(_pool)).addCollateral(4 * 1e18, _i9_91); - _assertLenderLpBalance( - { - lender: _lender1, - index: _i9_91, - lpBalance: 149.668739373743648296000000000 * 1e27, - depositTime: _startTime + 100 days + 10 hours + 1 hours - } - ); + + _pool.addQuoteToken(100 * 1e18, _i9_91, block.timestamp + 1 minutes); + _pool.moveQuoteToken(10 * 1e18, _i9_52, _i9_91, block.timestamp + 1 minutes); + ERC20Pool(address(_pool)).addCollateral(4 * 1e18, _i9_91, block.timestamp + 1 minutes); + + _assertLenderLpBalance({ + lender: _lender1, + index: _i9_91, + lpBalance: 149.668739373743648296 * 1e18, + depositTime: _startTime + 100 days + 10 hours + 1 hours + }); // bucket is healthy again - _assertBucket( - { - index: _i9_91, - lpBalance: 149.668739373743648296000000000 * 1e27, - collateral: 4 * 1e18, - deposit: 110.0000000000000000000000 * 1e18, - exchangeRate: 1.00000000000000000000000000 * 1e27 - } - ); + _assertBucket({ + index: _i9_91, + lpBalance: 149.668739373743648296 * 1e18, + collateral: 4 * 1e18, + deposit: 110 * 1e18, + exchangeRate: 1 * 1e18 + }); // when moving to a bucket that was marked insolvent, the deposit time should be the greater between from bucket deposit time and insolvency time + 1 changePrank(_lender); - _assertLenderLpBalance( - { - lender: _lender, - index: _i9_91, - lpBalance: 0, - depositTime: _startTime - } - ); - _pool.moveQuoteToken(1_000 * 1e18, _i9_52, _i9_91); - _assertLenderLpBalance( - { - lender: _lender, - index: _i9_91, - lpBalance: 1_000.000000000000000000000000000 * 1e27, - depositTime: _startTime + 100 days + 10 hours + 1 // _i9_91 bucket insolvency time + 1 (since deposit in _i9_52 from bucket was done before _i9_91 target bucket become insolvent) - } - ); - _pool.addQuoteToken(1_000 * 1e18, _i9_52); - _pool.moveQuoteToken(1_000 * 1e18, _i9_52, _i9_91); - _assertLenderLpBalance( - { - lender: _lender, - index: _i9_91, - lpBalance: 1_999.999999999999999999130185000 * 1e27, - depositTime: _startTime + 100 days + 10 hours + 1 hours // time of deposit in _i9_52 from bucket (since deposit in _i9_52 from bucket was done after _i9_91 target bucket become insolvent) - } - ); + _assertLenderLpBalance({ + lender: _lender, + index: _i9_91, + lpBalance: 0, + depositTime: _startTime + }); + + _pool.moveQuoteToken(1_000 * 1e18, _i9_52, _i9_91, block.timestamp + 1 minutes); + + _assertLenderLpBalance({ + lender: _lender, + index: _i9_91, + lpBalance: 1_000 * 1e18, + depositTime: _startTime + 100 days + 10 hours + 1 // _i9_91 bucket insolvency time + 1 (since deposit in _i9_52 from bucket was done before _i9_91 target bucket become insolvent) + }); + + _pool.addQuoteToken(1_000 * 1e18, _i9_52, block.timestamp + 1 minutes); + _pool.moveQuoteToken(1_000 * 1e18, _i9_52, _i9_91, block.timestamp + 1 minutes); + + _assertLenderLpBalance({ + lender: _lender, + index: _i9_91, + lpBalance: 2_000 * 1e18, + depositTime: _startTime + 100 days + 10 hours + 1 hours // time of deposit in _i9_52 from bucket (since deposit in _i9_52 from bucket was done after _i9_91 target bucket become insolvent) + }); + + // ensure bucket bankruptcy when moving amounts from an unbalanced bucket leave bucket healthy + _assertBucket({ + index: _i9_72, + lpBalance: 11_000 * 1e18, + collateral: 0 * 1e18, + deposit: 9_035.875749431291350856 * 1e18, + exchangeRate: 0.821443249948299214 * 1e18 + }); + + vm.expectEmit(true, true, false, true); + emit BucketBankruptcy(_i9_72, 3827); + _pool.moveQuoteToken(10000000000 * 1e18, _i9_72, _i9_91, type(uint256).max); + + _assertBucket({ + index: _i9_72, + lpBalance: 0, + collateral: 0 * 1e18, + deposit: 0, + exchangeRate: 1 * 1e18 + }); } } diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsTake.t.sol b/tests/forge/ERC20Pool/ERC20PoolLiquidationsTake.t.sol index 6a539dd8d..db55f6fd8 100644 --- a/tests/forge/ERC20Pool/ERC20PoolLiquidationsTake.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolLiquidationsTake.t.sol @@ -26,75 +26,57 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { _mintCollateralAndApproveTokens(_lender1, 4 * 1e18); // Lender adds Quote token accross 5 prices - _addInitialLiquidity( - { - from: _lender, - amount: 2_000 * 1e18, - index: _i9_91 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 5_000 * 1e18, - index: _i9_81 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 11_000 * 1e18, - index: _i9_72 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 25_000 * 1e18, - index: _i9_62 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 30_000 * 1e18, - index: _i9_52 - } - ); + _addInitialLiquidity({ + from: _lender, + amount: 2_000 * 1e18, + index: _i9_91 + }); + _addInitialLiquidity({ + from: _lender, + amount: 5_000 * 1e18, + index: _i9_81 + }); + _addInitialLiquidity({ + from: _lender, + amount: 11_000 * 1e18, + index: _i9_72 + }); + _addInitialLiquidity({ + from: _lender, + amount: 25_000 * 1e18, + index: _i9_62 + }); + _addInitialLiquidity({ + from: _lender, + amount: 30_000 * 1e18, + index: _i9_52 + }); // first borrower adds collateral token and borrows - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - amount: 2 * 1e18 - } - ); - _borrow( - { - from: _borrower, - amount: 19.25 * 1e18, - indexLimit: _i9_91, - newLup: 9.917184843435912074 * 1e18 - } - ); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + amount: 2 * 1e18 + }); + _borrow({ + from: _borrower, + amount: 19.25 * 1e18, + indexLimit: _i9_91, + newLup: 9.917184843435912074 * 1e18 + }); // second borrower adds collateral token and borrows - _pledgeCollateral( - { - from: _borrower2, - borrower: _borrower2, - amount: 1_000 * 1e18 - } - ); - _borrow( - { - from: _borrower2, - amount: 7_980 * 1e18, - indexLimit: _i9_72, - newLup: 9.721295865031779605 * 1e18 - } - ); + _pledgeCollateral({ + from: _borrower2, + borrower: _borrower2, + amount: 1_000 * 1e18 + }); + _borrow({ + from: _borrower2, + amount: 7_980 * 1e18, + indexLimit: _i9_72, + newLup: 9.721295865031779605 * 1e18 + }); /*****************************/ /*** Assert pre-kick state ***/ @@ -117,81 +99,68 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { interestRateUpdate: _startTime }) ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 19.268509615384615394 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.115967548076923081 * 1e18, - borrowerCollateralization: 1.009034539679184679 * 1e18 - } - ); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 7_987.673076923076926760 * 1e18, - borrowerCollateral: 1_000 * 1e18, - borrowert0Np: 8.471136974495192174 * 1e18, - borrowerCollateralization: 1.217037273735858713 * 1e18 - } - ); - _assertReserveAuction( - { - reserves: 7.691586538461542154 * 1e18, - claimableReserves : 0, - claimableReservesRemaining: 0, - auctionPrice: 0, - timeRemaining: 0 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 19.268509615384615394 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.115967548076923081 * 1e18, + borrowerCollateralization: 1.009034539679184679 * 1e18 + }); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 7_987.673076923076926760 * 1e18, + borrowerCollateral: 1_000 * 1e18, + borrowert0Np: 8.471136974495192174 * 1e18, + borrowerCollateralization: 1.217037273735858713 * 1e18 + }); + _assertReserveAuction({ + reserves: 7.691586538461542154 * 1e18, + claimableReserves : 0, + claimableReservesRemaining: 0, + auctionPrice: 0, + timeRemaining: 0 + }); + assertEq(_quote.balanceOf(_lender), 47_000 * 1e18); // should revert if there's no auction started - _assertTakeNoAuctionRevert( - { - from: _lender, - borrower: _borrower, - maxCollateral: 10 * 1e18 - } - ); + _assertTakeNoAuctionRevert({ + from: _lender, + borrower: _borrower, + maxCollateral: 10 * 1e18 + }); } function testTakeCoolDownPeriod() external tearDown { // should revert if there's no auction started - _assertTakeNoAuctionRevert( - { - from: _lender, - borrower: _borrower, - maxCollateral: 10 * 1e18 - } - ); + _assertTakeNoAuctionRevert({ + from: _lender, + borrower: _borrower, + maxCollateral: 10 * 1e18 + }); /********************/ /*** Kick Auction ***/ /********************/ - _borrow( - { - from: _borrower2, - amount: 1_700.0 * 1e18, - indexLimit: _p9_72, - newLup: _p9_72 - } - ); + _borrow({ + from: _borrower2, + amount: 1_700.0 * 1e18, + indexLimit: _i9_72, + newLup: _p9_72 + }); skip(100 days); - _kick( - { - from: _lender, - borrower: _borrower2, - debt: 9_945.738101507554206918 * 1e18, - collateral: 1_000 * 1e18, - bond: 98.229512113654856365 * 1e18, - transferAmount: 98.229512113654856365 * 1e18 - } - ); + _kick({ + from: _lender, + borrower: _borrower2, + debt: 9_945.738101507554206918 * 1e18, + collateral: 1_000 * 1e18, + bond: 98.229512113654856365 * 1e18, + transferAmount: 98.229512113654856365 * 1e18 + }); /********************/ /*** Take Auction ***/ @@ -200,54 +169,43 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { skip(30 minutes); // should revert if still in cool down period - _assertTakeAuctionInCooldownRevert( - { - from: _lender, - borrower: _borrower2, - maxCollateral: 10 * 1e18 - } - ); + _assertTakeAuctionInCooldownRevert({ + from: _lender, + borrower: _borrower2, + maxCollateral: 10 * 1e18 + }); } function testTakeLoanColConstraintBpfPosNoResidual() external tearDown { // Increase neutralPrice so it exceeds TP - _addLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: _i1505_26, - lpAward: 10_000 * 1e27, - newLup: _p1505_26 - } - ); - - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - amount: 1_000 * 1e18 - } - ); - - _borrow( - { - from: _borrower, - amount: 9_020 * 1e18, - indexLimit: _i9_72, - newLup: _p9_72 - } - ); + _addLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: _i1505_26, + lpAward: 10_000 * 1e18, + newLup: _p1505_26 + }); + + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + amount: 1_000 * 1e18 + }); + _borrow({ + from: _borrower, + amount: 9_020 * 1e18, + indexLimit: _i9_72, + newLup: _p9_72 + }); // calling borrow stamps loan with new t0NeutralPrice - _borrow( - { - from: _borrower2, - amount: 1_700.0 * 1e18, - indexLimit: _p9_72, - newLup: _p9_72 - } - ); + _borrow({ + from: _borrower2, + amount: 1_700.0 * 1e18, + indexLimit: _i9_72, + newLup: _p9_72 + }); _assertPool( PoolParams({ @@ -266,16 +224,13 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { interestRateUpdate: block.timestamp }) ); - - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 9_689.307692307692312160* 1e18, - borrowerCollateral: 1_000 * 1e18, - borrowert0Np: 1_575.326150647652569911 * 1e18, - borrowerCollateralization: 1.003301388885552947 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 9_689.307692307692312160* 1e18, + borrowerCollateral: 1_000 * 1e18, + borrowert0Np: 1_575.326150647652569911 * 1e18, + borrowerCollateralization: 1.003301388885552947 * 1e18 + }); skip(100 days); @@ -296,8 +251,6 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { interestRateUpdate: block.timestamp - 100 days }) ); - - _assertAuction( AuctionParams({ borrower: _borrower2, @@ -314,55 +267,42 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { neutralPrice: 0 }) ); - - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 9_822.951211365485636462* 1e18, - borrowerCollateral: 1_000 * 1e18, - borrowert0Np: 1_575.326150647652569911 * 1e18, - borrowerCollateralization: 0.989651241857326201 * 1e18 - } - ); - - _kick( - { - from: _lender, - borrower: _borrower2, - debt: 9_945.738101507554206918 * 1e18, - collateral: 1_000 * 1e18, - bond: 2_946.885363409645690939 * 1e18, - transferAmount: 2_946.885363409645690939 * 1e18 - } - ); - - _assertKicker( - { - kicker: _lender, - claimable: 0, - locked: 2_946.885363409645690939 * 1e18 - } - ); - - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 9_945.738101507554206918 * 1e18, - borrowerCollateral: 1_000 * 1e18, - borrowert0Np: 1_575.326150647652569911 * 1e18, - borrowerCollateralization: 0.977433325291186371 * 1e18 - } - ); - - _assertReserveAuction( - { - reserves: 176.383108065231049467 * 1e18, - claimableReserves : 80.790723478491074900 * 1e18, - claimableReservesRemaining: 0, - auctionPrice: 0, - timeRemaining: 0 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 9_822.951211365485636462* 1e18, + borrowerCollateral: 1_000 * 1e18, + borrowert0Np: 1_575.326150647652569911 * 1e18, + borrowerCollateralization: 0.989651241857326201 * 1e18 + }); + + _kick({ + from: _lender, + borrower: _borrower2, + debt: 9_945.738101507554206918 * 1e18, + collateral: 1_000 * 1e18, + bond: 2_946.885363409645690939 * 1e18, + transferAmount: 2_946.885363409645690939 * 1e18 + }); + + _assertKicker({ + kicker: _lender, + claimable: 0, + locked: 2_946.885363409645690939 * 1e18 + }); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 9_945.738101507554206918 * 1e18, + borrowerCollateral: 1_000 * 1e18, + borrowert0Np: 1_575.326150647652569911 * 1e18, + borrowerCollateralization: 0.977433325291186371 * 1e18 + }); + _assertReserveAuction({ + reserves: 176.383108065231049467 * 1e18, + claimableReserves : 80.790723478491074900 * 1e18, + claimableReservesRemaining: 0, + auctionPrice: 0, + timeRemaining: 0 + }); skip(47000 seconds); // 13.05 hrs @@ -383,7 +323,6 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { interestRateUpdate: block.timestamp - 47000 seconds }) ); - _assertAuction( AuctionParams({ borrower: _borrower2, @@ -400,29 +339,24 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { neutralPrice: 1_597.054445085392479852 * 1e18 }) ); - - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 9_946.405146835980073929 * 1e18, - borrowerCollateral: 1_000.000000000000000 * 1e18, - borrowert0Np: 1_575.326150647652569911 * 1e18, - borrowerCollateralization: 0.977367774740624830 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 9_946.405146835980073929 * 1e18, + borrowerCollateral: 1_000.000000000000000 * 1e18, + borrowert0Np: 1_575.326150647652569911 * 1e18, + borrowerCollateralization: 0.977367774740624830 * 1e18 + }); // BPF Positive, Loan Col constraint - _take( - { - from: _lender, - borrower: _borrower2, - maxCollateral: 1_001 * 1e18, - bondChange: 3_598.602058071496018124* 1e18, - givenAmount: 12_005.655124053999200000 * 1e18, - collateralTaken: 1000.0 * 1e18, - isReward: true - } - ); + _take({ + from: _lender, + borrower: _borrower2, + maxCollateral: 1_001 * 1e18, + bondChange: 3_598.602058071496018124* 1e18, + givenAmount: 12_005.655124053999200000 * 1e18, + collateralTaken: 1000.0 * 1e18, + isReward: true + }); // Residual is not collateralized, auction is active _assertAuction( @@ -441,58 +375,46 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { neutralPrice: 1_597.054445085392479852 * 1e18 }) ); - // Bad debt remains - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 2_235.600441131995497104 * 1e18, - borrowerCollateral: 0, - borrowert0Np: 1_575.326150647652569911 * 1e18, - borrowerCollateralization: 0 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 2_235.600441131995497104 * 1e18, + borrowerCollateral: 0, + borrowert0Np: 1_575.326150647652569911 * 1e18, + borrowerCollateralization: 0 + }); } function testTakeCallerColConstraintBpfPosNoResidual() external tearDown { // Increase neutralPrice so it exceeds TP - _addLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: _i1505_26, - lpAward: 10_000 * 1e27, - newLup: _p1505_26 - } - ); - - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - amount: 1_000 * 1e18 - } - ); - - _borrow( - { - from: _borrower, - amount: 9_020 * 1e18, - indexLimit: _i9_72, - newLup: _p9_72 - } - ); + _addLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: _i1505_26, + lpAward: 10_000 * 1e18, + newLup: _p1505_26 + }); + + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + amount: 1_000 * 1e18 + }); + _borrow({ + from: _borrower, + amount: 9_020 * 1e18, + indexLimit: _i9_72, + newLup: _p9_72 + }); // calling borrow stamps loan with new t0NeutralPrice - _borrow( - { - from: _borrower2, - amount: 1_700.0 * 1e18, - indexLimit: _p9_72, - newLup: _p9_72 - } - ); + _borrow({ + from: _borrower2, + amount: 1_700.0 * 1e18, + indexLimit: _i9_72, + newLup: _p9_72 + }); _assertPool( PoolParams({ @@ -511,16 +433,13 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { interestRateUpdate: block.timestamp }) ); - - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 9_689.307692307692312160* 1e18, - borrowerCollateral: 1_000 * 1e18, - borrowert0Np: 1_575.326150647652569911 * 1e18, - borrowerCollateralization: 1.003301388885552947 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 9_689.307692307692312160* 1e18, + borrowerCollateral: 1_000 * 1e18, + borrowert0Np: 1_575.326150647652569911 * 1e18, + borrowerCollateralization: 1.003301388885552947 * 1e18 + }); skip(100 days); @@ -541,8 +460,6 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { interestRateUpdate: block.timestamp - 100 days }) ); - - _assertAuction( AuctionParams({ borrower: _borrower2, @@ -559,55 +476,42 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { neutralPrice: 0 }) ); - - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 9_822.951211365485636462* 1e18, - borrowerCollateral: 1_000 * 1e18, - borrowert0Np: 1_575.326150647652569911 * 1e18, - borrowerCollateralization: 0.989651241857326201 * 1e18 - } - ); - - _kick( - { - from: _lender, - borrower: _borrower2, - debt: 9_945.738101507554206918 * 1e18, - collateral: 1_000 * 1e18, - bond: 2_946.885363409645690939 * 1e18, - transferAmount: 2_946.885363409645690939 * 1e18 - } - ); - - _assertKicker( - { - kicker: _lender, - claimable: 0, - locked: 2_946.885363409645690939 * 1e18 - } - ); - - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 9_945.738101507554206918 * 1e18, - borrowerCollateral: 1_000 * 1e18, - borrowert0Np: 1_575.326150647652569911 * 1e18, - borrowerCollateralization: 0.977433325291186371 * 1e18 - } - ); - - _assertReserveAuction( - { - reserves: 176.383108065231049467 * 1e18, - claimableReserves : 80.790723478491074900 * 1e18, - claimableReservesRemaining: 0, - auctionPrice: 0, - timeRemaining: 0 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 9_822.951211365485636462* 1e18, + borrowerCollateral: 1_000 * 1e18, + borrowert0Np: 1_575.326150647652569911 * 1e18, + borrowerCollateralization: 0.989651241857326201 * 1e18 + }); + + _kick({ + from: _lender, + borrower: _borrower2, + debt: 9_945.738101507554206918 * 1e18, + collateral: 1_000 * 1e18, + bond: 2_946.885363409645690939 * 1e18, + transferAmount: 2_946.885363409645690939 * 1e18 + }); + + _assertKicker({ + kicker: _lender, + claimable: 0, + locked: 2_946.885363409645690939 * 1e18 + }); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 9_945.738101507554206918 * 1e18, + borrowerCollateral: 1_000 * 1e18, + borrowert0Np: 1_575.326150647652569911 * 1e18, + borrowerCollateralization: 0.977433325291186371 * 1e18 + }); + _assertReserveAuction({ + reserves: 176.383108065231049467 * 1e18, + claimableReserves : 80.790723478491074900 * 1e18, + claimableReservesRemaining: 0, + auctionPrice: 0, + timeRemaining: 0 + }); skip(43000 seconds); // 11.94 hrs @@ -628,7 +532,6 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { interestRateUpdate: block.timestamp - 43000 seconds }) ); - _assertAuction( AuctionParams({ borrower: _borrower2, @@ -645,29 +548,24 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { neutralPrice: 1_597.054445085392479852 * 1e18 }) ); - - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 9_946.348375279124882460 * 1e18, - borrowerCollateral: 1_000.000000000000000 * 1e18, - borrowert0Np: 1_575.326150647652569911 * 1e18, - borrowerCollateralization: 0.977373353339734632 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 9_946.348375279124882460 * 1e18, + borrowerCollateral: 1_000.000000000000000 * 1e18, + borrowert0Np: 1_575.326150647652569911 * 1e18, + borrowerCollateralization: 0.977373353339734632 * 1e18 + }); // BPF Positive, caller collateral is constraint - _take( - { - from: _lender, - borrower: _borrower2, - maxCollateral: 10 * 1e18, - bondChange: 77.051043093949465946 * 1e18, - givenAmount: 259.336494770337503360 * 1e18, - collateralTaken: 10.0 * 1e18, - isReward: true - } - ); + _take({ + from: _lender, + borrower: _borrower2, + maxCollateral: 10 * 1e18, + bondChange: 77.051043093949465946 * 1e18, + givenAmount: 259.336494770337503360 * 1e18, + collateralTaken: 10.0 * 1e18, + isReward: true + }); // Residual is not collateralized, auction is active _assertAuction( @@ -686,57 +584,45 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { neutralPrice: 1_597.054445085392479852 * 1e18 }) ); - - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 10_460.307309872275586822 * 1e18, - borrowerCollateral: 990 * 1e18, - borrowert0Np: 1_575.326150647652569911 * 1e18, - borrowerCollateralization: 0.920057377023560467 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 10_460.307309872275586822 * 1e18, + borrowerCollateral: 990 * 1e18, + borrowert0Np: 1_575.326150647652569911 * 1e18, + borrowerCollateralization: 0.920057377023560467 * 1e18 + }); } function testTakeCallerColConstraintBpfPosResidual () external tearDown { // Increase neutralPrice so it exceeds TP - _addLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: _i1505_26, - lpAward: 10_000 * 1e27, - newLup: _p1505_26 - } - ); - - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - amount: 1_000 * 1e18 - } - ); - - _borrow( - { - from: _borrower, - amount: 9_020 * 1e18, - indexLimit: _i9_72, - newLup: _p9_72 - } - ); + _addLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: _i1505_26, + lpAward: 10_000 * 1e18, + newLup: _p1505_26 + }); + + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + amount: 1_000 * 1e18 + }); + _borrow({ + from: _borrower, + amount: 9_020 * 1e18, + indexLimit: _i9_72, + newLup: _p9_72 + }); // calling borrow stamps loan with new t0NeutralPrice - _borrow( - { - from: _borrower2, - amount: 1_700.0 * 1e18, - indexLimit: _p9_72, - newLup: _p9_72 - } - ); + _borrow({ + from: _borrower2, + amount: 1_700.0 * 1e18, + indexLimit: _i9_72, + newLup: _p9_72 + }); _assertPool( PoolParams({ @@ -755,16 +641,13 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { interestRateUpdate: block.timestamp }) ); - - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 9_689.307692307692312160* 1e18, - borrowerCollateral: 1_000 * 1e18, - borrowert0Np: 1_575.326150647652569911 * 1e18, - borrowerCollateralization: 1.003301388885552947 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 9_689.307692307692312160* 1e18, + borrowerCollateral: 1_000 * 1e18, + borrowert0Np: 1_575.326150647652569911 * 1e18, + borrowerCollateralization: 1.003301388885552947 * 1e18 + }); skip(100 days); @@ -785,8 +668,6 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { interestRateUpdate: block.timestamp - 100 days }) ); - - _assertAuction( AuctionParams({ borrower: _borrower2, @@ -803,55 +684,42 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { neutralPrice: 0 }) ); - - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 9_822.951211365485636462* 1e18, - borrowerCollateral: 1_000 * 1e18, - borrowert0Np: 1_575.326150647652569911 * 1e18, - borrowerCollateralization: 0.989651241857326201 * 1e18 - } - ); - - _kick( - { - from: _lender, - borrower: _borrower2, - debt: 9_945.738101507554206918 * 1e18, - collateral: 1_000 * 1e18, - bond: 2_946.885363409645690939 * 1e18, - transferAmount: 2_946.885363409645690939 * 1e18 - } - ); - - _assertKicker( - { - kicker: _lender, - claimable: 0, - locked: 2_946.885363409645690939 * 1e18 - } - ); - - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 9_945.738101507554206918 * 1e18, - borrowerCollateral: 1_000 * 1e18, - borrowert0Np: 1_575.326150647652569911 * 1e18, - borrowerCollateralization: 0.977433325291186371 * 1e18 - } - ); - - _assertReserveAuction( - { - reserves: 176.383108065231049467 * 1e18, - claimableReserves : 80.790723478491074900 * 1e18, - claimableReservesRemaining: 0, - auctionPrice: 0, - timeRemaining: 0 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 9_822.951211365485636462* 1e18, + borrowerCollateral: 1_000 * 1e18, + borrowert0Np: 1_575.326150647652569911 * 1e18, + borrowerCollateralization: 0.989651241857326201 * 1e18 + }); + + _kick({ + from: _lender, + borrower: _borrower2, + debt: 9_945.738101507554206918 * 1e18, + collateral: 1_000 * 1e18, + bond: 2_946.885363409645690939 * 1e18, + transferAmount: 2_946.885363409645690939 * 1e18 + }); + + _assertKicker({ + kicker: _lender, + claimable: 0, + locked: 2_946.885363409645690939 * 1e18 + }); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 9_945.738101507554206918 * 1e18, + borrowerCollateral: 1_000 * 1e18, + borrowert0Np: 1_575.326150647652569911 * 1e18, + borrowerCollateralization: 0.977433325291186371 * 1e18 + }); + _assertReserveAuction({ + reserves: 176.383108065231049467 * 1e18, + claimableReserves : 80.790723478491074900 * 1e18, + claimableReservesRemaining: 0, + auctionPrice: 0, + timeRemaining: 0 + }); skip(43000 seconds); // 11.94 hrs @@ -872,7 +740,6 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { interestRateUpdate: block.timestamp - 43000 seconds }) ); - _assertAuction( AuctionParams({ borrower: _borrower2, @@ -889,29 +756,24 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { neutralPrice: 1_597.054445085392479852 * 1e18 }) ); - - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 9_946.348375279124882460 * 1e18, - borrowerCollateral: 1_000.000000000000000 * 1e18, - borrowert0Np: 1_575.326150647652569911 * 1e18, - borrowerCollateralization: 0.977373353339734632 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 9_946.348375279124882460 * 1e18, + borrowerCollateral: 1_000.000000000000000 * 1e18, + borrowert0Np: 1_575.326150647652569911 * 1e18, + borrowerCollateralization: 0.977373353339734632 * 1e18 + }); // BPF Positive, Caller Col constraint - _take( - { - from: _lender, - borrower: _borrower2, - maxCollateral: 577.0 * 1e18, - bondChange: 4_445.845186520884185062 * 1e18, - givenAmount: 14_963.715748248473943872 * 1e18, - collateralTaken: 577.000000000000000000 * 1e18, - isReward: true - } - ); + _take({ + from: _lender, + borrower: _borrower2, + maxCollateral: 577.0 * 1e18, + bondChange: 4_445.845186520884185062 * 1e18, + givenAmount: 14_963.715748248473943872 * 1e18, + collateralTaken: 577.000000000000000000 * 1e18, + isReward: true + }); // Residual is collateralized, auction is not active _assertAuction( @@ -930,29 +792,24 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { neutralPrice: 0 }) ); - - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 124.722199821073865675 * 1e18, - borrowerCollateral: 423.000000000000000000 * 1e18, - borrowert0Np: 0.303909165615512483 * 1e18, - borrowerCollateralization: 5_105.158167959369510853 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 124.722199821073865675 * 1e18, + borrowerCollateral: 423.000000000000000000 * 1e18, + borrowert0Np: 0.303909165615512483 * 1e18, + borrowerCollateralization: 5_105.158167959369510853 * 1e18 + }); } function testTakeCallerColConstraintBpfNegResidual () external tearDown { - _borrow( - { - from: _borrower2, - amount: 1_700.0 * 1e18, - indexLimit: _p9_72, - newLup: _p9_72 - } - ); + _borrow({ + from: _borrower2, + amount: 1_700.0 * 1e18, + indexLimit: _i9_72, + newLup: _p9_72 + }); _assertPool( PoolParams({ @@ -971,29 +828,24 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { interestRateUpdate: block.timestamp }) ); - - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 9_689.307692307692312160 * 1e18, - borrowerCollateral: 1_000 * 1e18, - borrowert0Np: 10.275765152019230606 * 1e18, - borrowerCollateralization: 1.003301388885552947 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 9_689.307692307692312160 * 1e18, + borrowerCollateral: 1_000 * 1e18, + borrowert0Np: 10.275765152019230606 * 1e18, + borrowerCollateralization: 1.003301388885552947 * 1e18 + }); skip(100 days); - _kick( - { - from: _lender, - borrower: _borrower2, - debt: 9_945.738101507554206918 * 1e18, - collateral: 1_000 * 1e18, - bond: 98.229512113654856365 * 1e18, - transferAmount: 98.229512113654856365 * 1e18 - } - ); + _kick({ + from: _lender, + borrower: _borrower2, + debt: 9_945.738101507554206918 * 1e18, + collateral: 1_000 * 1e18, + bond: 98.229512113654856365 * 1e18, + transferAmount: 98.229512113654856365 * 1e18 + }); _assertAuction( AuctionParams({ @@ -1011,31 +863,25 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { neutralPrice: 10.417497612122395691 * 1e18 }) ); - _assertKicker( - { - kicker: _lender, - claimable: 0, - locked: 98.229512113654856365 * 1e18 - } - ); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 9_945.738101507554206918 * 1e18, - borrowerCollateral: 1_000 * 1e18, - borrowert0Np: 10.275765152019230606 * 1e18, - borrowerCollateralization: 0.977433325291186371 * 1e18 - } - ); - _assertReserveAuction( - { - reserves: 147.625795655539437491 * 1e18, - claimableReserves : 97.799433758115930094 * 1e18, - claimableReservesRemaining: 0, - auctionPrice: 0, - timeRemaining: 0 - } - ); + _assertKicker({ + kicker: _lender, + claimable: 0, + locked: 98.229512113654856365 * 1e18 + }); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 9_945.738101507554206918 * 1e18, + borrowerCollateral: 1_000 * 1e18, + borrowert0Np: 10.275765152019230606 * 1e18, + borrowerCollateralization: 0.977433325291186371 * 1e18 + }); + _assertReserveAuction({ + reserves: 147.625795655539437491 * 1e18, + claimableReserves : 97.799433758115930094 * 1e18, + claimableReservesRemaining: 0, + auctionPrice: 0, + timeRemaining: 0 + }); skip(2 hours); @@ -1056,7 +902,6 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { interestRateUpdate: block.timestamp - 2 hours }) ); - _assertAuction( AuctionParams({ borrower: _borrower2, @@ -1073,29 +918,24 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { neutralPrice: 10.417497612122395691 * 1e18 }) ); - - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 9_945.840284273233679079 * 1e18, - borrowerCollateral: 1_000.000000000000000 * 1e18, - borrowert0Np: 10.275765152019230606 * 1e18, - borrowerCollateralization: 0.977423283219567398 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 9_945.840284273233679079 * 1e18, + borrowerCollateral: 1_000.000000000000000 * 1e18, + borrowert0Np: 10.275765152019230606 * 1e18, + borrowerCollateralization: 0.977423283219567398 * 1e18 + }); // BPF Negative, Caller collateral constraint - _take( - { - from: _lender, - borrower: _borrower2, - maxCollateral: 10.0 * 1e18, - bondChange: 16.667996179395833107 * 1e18, - givenAmount: 1_666.799617939583310720 * 1e18, - collateralTaken: 10.0 * 1e18, - isReward: false - } - ); + _take({ + from: _lender, + borrower: _borrower2, + maxCollateral: 10.0 * 1e18, + bondChange: 16.667996179395833107 * 1e18, + givenAmount: 1_666.799617939583310720 * 1e18, + collateralTaken: 10.0 * 1e18, + isReward: false + }); _assertPool( PoolParams({ @@ -1114,7 +954,6 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { interestRateUpdate: block.timestamp - 2 hours }) ); - // Residual is collateralized, auction is not active _assertAuction( AuctionParams({ @@ -1132,58 +971,46 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { neutralPrice: 0 }) ); - - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 8_975.249486232776725894 * 1e18, - borrowerCollateral: 990.000000000000000000 * 1e18, - borrowert0Np: 9.438566662973887791 * 1e18, - borrowerCollateralization: 1.072291407736791833 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 8_975.249486232776725894 * 1e18, + borrowerCollateral: 990.000000000000000000 * 1e18, + borrowert0Np: 9.438566662973887791 * 1e18, + borrowerCollateralization: 1.072291407736791833 * 1e18 + }); } function testTakeLoanDebtConstraintBpfPosResidual() external tearDown { // Increase neutralPrice so it exceeds TP - _addLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: _i1505_26, - lpAward: 10_000 * 1e27, - newLup: _p1505_26 - } - ); - - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - amount: 1_000 * 1e18 - } - ); - - _borrow( - { - from: _borrower, - amount: 9_020 * 1e18, - indexLimit: _i9_72, - newLup: _p9_72 - } - ); + _addLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: _i1505_26, + lpAward: 10_000 * 1e18, + newLup: _p1505_26 + }); + + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + amount: 1_000 * 1e18 + }); + _borrow({ + from: _borrower, + amount: 9_020 * 1e18, + indexLimit: _i9_72, + newLup: _p9_72 + }); // calling borrow stamps loan with new t0NeutralPrice - _borrow( - { - from: _borrower2, - amount: 1_700.0 * 1e18, - indexLimit: _p9_72, - newLup: _p9_72 - } - ); + _borrow({ + from: _borrower2, + amount: 1_700.0 * 1e18, + indexLimit: _i9_72, + newLup: _p9_72 + }); _assertPool( PoolParams({ @@ -1202,16 +1029,13 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { interestRateUpdate: block.timestamp }) ); - - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 9_689.307692307692312160* 1e18, - borrowerCollateral: 1_000 * 1e18, - borrowert0Np: 1_575.326150647652569911 * 1e18, - borrowerCollateralization: 1.003301388885552947 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 9_689.307692307692312160* 1e18, + borrowerCollateral: 1_000 * 1e18, + borrowert0Np: 1_575.326150647652569911 * 1e18, + borrowerCollateralization: 1.003301388885552947 * 1e18 + }); skip(100 days); @@ -1232,8 +1056,6 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { interestRateUpdate: block.timestamp - 100 days }) ); - - _assertAuction( AuctionParams({ borrower: _borrower2, @@ -1250,55 +1072,42 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { neutralPrice: 0 }) ); - - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 9_822.951211365485636462* 1e18, - borrowerCollateral: 1_000 * 1e18, - borrowert0Np: 1_575.326150647652569911 * 1e18, - borrowerCollateralization: 0.989651241857326201 * 1e18 - } - ); - - _kick( - { - from: _lender, - borrower: _borrower2, - debt: 9_945.738101507554206918 * 1e18, - collateral: 1_000 * 1e18, - bond: 2_946.885363409645690939 * 1e18, - transferAmount: 2_946.885363409645690939 * 1e18 - } - ); - - _assertKicker( - { - kicker: _lender, - claimable: 0, - locked: 2_946.885363409645690939 * 1e18 - } - ); - - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 9_945.738101507554206918 * 1e18, - borrowerCollateral: 1_000 * 1e18, - borrowert0Np: 1_575.326150647652569911 * 1e18, - borrowerCollateralization: 0.977433325291186371 * 1e18 - } - ); - - _assertReserveAuction( - { - reserves: 176.383108065231049467 * 1e18, - claimableReserves : 80.790723478491074900 * 1e18, - claimableReservesRemaining: 0, - auctionPrice: 0, - timeRemaining: 0 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 9_822.951211365485636462* 1e18, + borrowerCollateral: 1_000 * 1e18, + borrowert0Np: 1_575.326150647652569911 * 1e18, + borrowerCollateralization: 0.989651241857326201 * 1e18 + }); + + _kick({ + from: _lender, + borrower: _borrower2, + debt: 9_945.738101507554206918 * 1e18, + collateral: 1_000 * 1e18, + bond: 2_946.885363409645690939 * 1e18, + transferAmount: 2_946.885363409645690939 * 1e18 + }); + + _assertKicker({ + kicker: _lender, + claimable: 0, + locked: 2_946.885363409645690939 * 1e18 + }); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 9_945.738101507554206918 * 1e18, + borrowerCollateral: 1_000 * 1e18, + borrowert0Np: 1_575.326150647652569911 * 1e18, + borrowerCollateralization: 0.977433325291186371 * 1e18 + }); + _assertReserveAuction({ + reserves: 176.383108065231049467 * 1e18, + claimableReserves : 80.790723478491074900 * 1e18, + claimableReservesRemaining: 0, + auctionPrice: 0, + timeRemaining: 0 + }); skip(43000 seconds); // 11.94 hrs @@ -1319,7 +1128,6 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { interestRateUpdate: block.timestamp - 43000 seconds }) ); - _assertAuction( AuctionParams({ borrower: _borrower2, @@ -1336,29 +1144,24 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { neutralPrice: 1_597.054445085392479852 * 1e18 }) ); - - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 9_946.348375279124882460 * 1e18, - borrowerCollateral: 1_000.000000000000000 * 1e18, - borrowert0Np: 1_575.326150647652569911 * 1e18, - borrowerCollateralization: 0.977373353339734632 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 9_946.348375279124882460 * 1e18, + borrowerCollateral: 1_000.000000000000000 * 1e18, + borrowert0Np: 1_575.326150647652569911 * 1e18, + borrowerCollateralization: 0.977373353339734632 * 1e18 + }); // BPF Positive, Loan Debt constraint - _take( - { - from: _lender, - borrower: _borrower2, - maxCollateral: 1_001 * 1e18, - bondChange: 4_498.564564314381167419 * 1e18, - givenAmount: 15_141.157325863044791651 * 1e18, - collateralTaken: 583.842136806534270091 * 1e18, - isReward: true - } - ); + _take({ + from: _lender, + borrower: _borrower2, + maxCollateral: 1_001 * 1e18, + bondChange: 4_498.564564314381167419 * 1e18, + givenAmount: 15_141.157325863044791651 * 1e18, + collateralTaken: 583.842136806534270091 * 1e18, + isReward: true + }); // Residual is collateralized, auction is not active _assertAuction( @@ -1377,51 +1180,45 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { neutralPrice: 0 }) ); - - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 0, - borrowerCollateral: 416.157863193465729909 * 1e18, - borrowert0Np: 0, - borrowerCollateralization: 1 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 0, + borrowerCollateral: 416.157863193465729909 * 1e18, + borrowert0Np: 0, + borrowerCollateralization: 1 * 1e18 + }); } function testTakeAndSettle() external tearDown { // Borrower2 borrows - _borrow( - { - from: _borrower2, - amount: 1_730 * 1e18, - indexLimit: _i9_72, - newLup: 9.721295865031779605 * 1e18 - } - ); + _borrow({ + from: _borrower2, + amount: 1_730 * 1e18, + indexLimit: _i9_72, + newLup: 9.721295865031779605 * 1e18 + }); // Skip to make borrower undercollateralized skip(100 days); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 9_853.394241979221645666 * 1e18, - borrowerCollateral: 1_000 * 1e18, - borrowert0Np: 10.307611531622595991 * 1e18, - borrowerCollateralization: 0.986593617011217057 * 1e18 - } - ); - _kick( - { - from: _lender, - borrower: _borrower2, - debt: 9_976.561670003961916237 * 1e18, - collateral: 1_000 * 1e18, - bond: 98.533942419792216457 * 1e18, - transferAmount: 98.533942419792216457 * 1e18 - } - ); + + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 9_853.394241979221645666 * 1e18, + borrowerCollateral: 1_000 * 1e18, + borrowert0Np: 10.307611531622595991 * 1e18, + borrowerCollateralization: 0.986593617011217057 * 1e18 + }); + + _kick({ + from: _lender, + borrower: _borrower2, + debt: 9_976.561670003961916237 * 1e18, + collateral: 1_000 * 1e18, + bond: 98.533942419792216457 * 1e18, + transferAmount: 98.533942419792216457 * 1e18 + }); + _assertAuction( AuctionParams({ borrower: _borrower2, @@ -1438,33 +1235,28 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { neutralPrice: 10.449783245217816340 * 1e18 }) ); - _assertKicker( - { - kicker: _lender, - claimable: 0, - locked: 98.533942419792216457 * 1e18 - } - ); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 9_976.561670003961916237 * 1e18, - borrowerCollateral: 1_000 * 1e18, - borrowert0Np: 10.307611531622595991 * 1e18, - borrowerCollateralization: 0.974413448899967463 * 1e18 - } - ); - _assertReserveAuction( - { - reserves: 148.064352861909228810 * 1e18, - claimableReserves : 98.083873122003682866 * 1e18, - claimableReservesRemaining: 0, - auctionPrice: 0, - timeRemaining: 0 - } - ); + _assertKicker({ + kicker: _lender, + claimable: 0, + locked: 98.533942419792216457 * 1e18 + }); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 9_976.561670003961916237 * 1e18, + borrowerCollateral: 1_000 * 1e18, + borrowert0Np: 10.307611531622595991 * 1e18, + borrowerCollateralization: 0.974413448899967463 * 1e18 + }); + _assertReserveAuction({ + reserves: 148.064352861909228810 * 1e18, + claimableReserves : 98.083873122003682866 * 1e18, + claimableReservesRemaining: 0, + auctionPrice: 0, + timeRemaining: 0 + }); uint256 preTakeSnapshot = vm.snapshot(); + skip(364 minutes); _assertAuction( @@ -1483,38 +1275,23 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { neutralPrice: 10.449783245217816340 * 1e18 }) ); - - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 9_976.872588243234769567 * 1e18, - borrowerCollateral: 1_000 * 1e18, - borrowert0Np: 10.307611531622595991 * 1e18, - borrowerCollateralization: 0.974383082378677738 * 1e18 - } - ); - - _take( - { - from: _lender, - borrower: _borrower2, - maxCollateral: 1_000 * 1e18, - bondChange: 99.778877943799773760 * 1e18, - givenAmount: 9_977.887794379977376000 * 1e18, - collateralTaken: 1000.0 * 1e18, - isReward: true - } - ); - - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 797.144752984083601436 * 1e18, - borrowerCollateral: 0, - borrowert0Np: 10.307611531622595991 * 1e18, - borrowerCollateralization: 0 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 9_976.872588243234769567 * 1e18, + borrowerCollateral: 1_000 * 1e18, + borrowert0Np: 10.307611531622595991 * 1e18, + borrowerCollateralization: 0.974383082378677738 * 1e18 + }); + + _take({ + from: _lender, + borrower: _borrower2, + maxCollateral: 1_000 * 1e18, + bondChange: 99.778877943799773760 * 1e18, + givenAmount: 9_977.887794379977376000 * 1e18, + collateralTaken: 1000.0 * 1e18, + isReward: true + }); _assertAuction( AuctionParams({ @@ -1532,14 +1309,18 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { neutralPrice: 10.449783245217816340 * 1e18 }) ); - - _assertKicker( - { - kicker: _lender, - claimable: 0, - locked: 198.312820363591990217 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 797.144752984083601436 * 1e18, + borrowerCollateral: 0, + borrowert0Np: 10.307611531622595991 * 1e18, + borrowerCollateralization: 0 + }); + _assertKicker({ + kicker: _lender, + claimable: 0, + locked: 198.312820363591990217 * 1e18 + }); vm.revertTo(preTakeSnapshot); @@ -1562,30 +1343,26 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { neutralPrice: 10.449783245217816340 * 1e18 }) ); - - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 9_977.074177773911990381 * 1e18, - borrowerCollateral: 1_000 * 1e18, - borrowert0Np: 10.307611531622595991 * 1e18, - borrowerCollateralization: 0.974363394700228467 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 9_977.074177773911990381 * 1e18, + borrowerCollateral: 1_000 * 1e18, + borrowert0Np: 10.307611531622595991 * 1e18, + borrowerCollateralization: 0.974363394700228467 * 1e18 + }); // partial take for 20 collateral // Collateral amount is restrained by taker - _take( - { - from: _lender, - borrower: _borrower2, - maxCollateral: 20 * 1e18, - bondChange: 0.130622290565222707 * 1e18, - givenAmount: 13.062229056522270720 * 1e18, - collateralTaken: 20 * 1e18, - isReward: true - } - ); + _take({ + from: _lender, + borrower: _borrower2, + maxCollateral: 20 * 1e18, + bondChange: 0.130622290565222707 * 1e18, + givenAmount: 13.062229056522270720 * 1e18, + collateralTaken: 20 * 1e18, + isReward: true + }); + _assertAuction( AuctionParams({ borrower: _borrower2, @@ -1602,46 +1379,38 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { neutralPrice: 10.449783245217816340 * 1e18 }) ); - _assertKicker( - { - kicker: _lender, - claimable: 0, - locked: 98.664564710357439164 * 1e18 // locked bond + reward, auction is not yet finished - } - ); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 10_662.537763452128781688 * 1e18, - borrowerCollateral: 980 * 1e18, - borrowert0Np: 10.307611531622595991 * 1e18, - borrowerCollateralization: 0.893489913853932440 * 1e18 - } - ); - + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 10_662.537763452128781688 * 1e18, + borrowerCollateral: 980 * 1e18, + borrowert0Np: 10.307611531622595991 * 1e18, + borrowerCollateralization: 0.893489913853932440 * 1e18 + }); + _assertKicker({ + kicker: _lender, + claimable: 0, + locked: 98.664564710357439164 * 1e18 // locked bond + reward, auction is not yet finished + }); // reserves should increase after take action - _assertReserveAuction( - { - reserves: 846.536571996419330152 * 1e18, - claimableReserves : 793.126206771778158186 * 1e18, - claimableReservesRemaining: 0, - auctionPrice: 0, - timeRemaining: 0 - } - ); + _assertReserveAuction({ + reserves: 846.536571996419330152 * 1e18, + claimableReserves : 793.126206771778158186 * 1e18, + claimableReservesRemaining: 0, + auctionPrice: 0, + timeRemaining: 0 + }); // take remaining collateral - _take( - { - from: _lender, - borrower: _borrower2, - maxCollateral: 981 * 1e18, - bondChange: 6.400492237695912653 * 1e18, - givenAmount: 640.049223769591265280 * 1e18, - collateralTaken: 980 * 1e18, - isReward: true - } - ); + _take({ + from: _lender, + borrower: _borrower2, + maxCollateral: 981 * 1e18, + bondChange: 6.400492237695912653 * 1e18, + givenAmount: 640.049223769591265280 * 1e18, + collateralTaken: 980 * 1e18, + isReward: true + }); + _assertAuction( AuctionParams({ borrower: _borrower2, @@ -1658,62 +1427,51 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { neutralPrice: 10.449783245217816340 * 1e18 }) ); - _assertKicker( - { - kicker: _lender, - claimable: 0, - locked: 105.065056948053351817 * 1e18 // locked bond + reward, auction is not yet finalized - } - ); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 10_028.889031920233428707 * 1e18, - borrowerCollateral: 0, - borrowert0Np: 10.307611531622595991 * 1e18, - borrowerCollateralization: 0 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 10_028.889031920233428707 * 1e18, + borrowerCollateral: 0, + borrowert0Np: 10.307611531622595991 * 1e18, + borrowerCollateralization: 0 + }); + _assertKicker({ + kicker: _lender, + claimable: 0, + locked: 105.065056948053351817 * 1e18 // locked bond + reward, auction is not yet finalized + }); // reserves should increase after take action - _assertReserveAuction( - { - reserves: 846.536571996419329799 * 1e18, - claimableReserves : 796.294450429437634598 * 1e18, - claimableReservesRemaining: 0, - auctionPrice: 0, - timeRemaining: 0 - } - ); + _assertReserveAuction({ + reserves: 846.536571996419329799 * 1e18, + claimableReserves : 796.294450429437634598 * 1e18, + claimableReservesRemaining: 0, + auctionPrice: 0, + timeRemaining: 0 + }); // should revert if there's no more collateral to be auctioned - _assertTakeInsufficentCollateralRevert( - { - from: _lender, - borrower: _borrower2, - maxCollateral: 10 * 1e18 - } - ); + _assertTakeInsufficentCollateralRevert({ + from: _lender, + borrower: _borrower2, + maxCollateral: 10 * 1e18 + }); // full clear / debt settle uint256 postTakeSnapshot = vm.snapshot(); - _assertBucket( - { - index: 3_696, - lpBalance: 2_000 * 1e27, - collateral: 0, - deposit: 2_118.911507166546112000 * 1e18, - exchangeRate: 1.059455753583273056000000000 * 1e27 - } - ); - _settle( - { - from: _lender, - borrower: _borrower2, - maxDepth: 10, - settledDebt: 9_891.935520844277346922 * 1e18 - } - ); + _assertBucket({ + index: 3_696, + lpBalance: 2_000 * 1e18, + collateral: 0, + deposit: 2_118.911507166546112000 * 1e18, + exchangeRate: 1.059455753583273056 * 1e18 + }); + + _settle({ + from: _lender, + borrower: _borrower2, + maxDepth: 10, + settledDebt: 9_891.935520844277346922 * 1e18 + }); _assertAuction( AuctionParams({ @@ -1731,137 +1489,109 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { neutralPrice: 0 }) ); - _assertKicker( - { - kicker: _lender, - claimable: 105.065056948053351817 * 1e18, - locked: 0 - } - ); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 0, - borrowerCollateral: 0, - borrowert0Np: 10.307611531622595991 * 1e18, - borrowerCollateralization: 1 * 1e18 - } - ); - - _assertBucket( - { - index: _i9_91, - lpBalance: 0, // bucket is bankrupt - collateral: 0, - deposit: 0, - exchangeRate: 1 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: _i9_91, - lpBalance: 0, // bucket is bankrupt - depositTime: _startTime - } - ); - _assertBucket( - { - index: _i9_81, - lpBalance: 0, // bucket is bankrupt - collateral: 0, - deposit: 0, - exchangeRate: 1 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: _i9_81, - lpBalance: 0, // bucket is bankrupt - depositTime: _startTime - } - ); - _assertBucket( - { - index: _i9_72, - lpBalance: 11_000 * 1e27, - collateral: 0, - deposit: 8_935.875749431291350857 * 1e18, - exchangeRate: 0.812352340857390122805181818 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: _i9_72, - lpBalance: 11_000 * 1e27, - depositTime: _startTime - } - ); - _assertBucket( - { - index: _i9_62, - lpBalance: 25_000 * 1e27, - collateral: 0, - deposit: 25_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: _i9_62, - lpBalance: 25_000 * 1e27, - depositTime: _startTime - } - ); - _assertBucket( - { - index: _i9_52, - lpBalance: 30_000 * 1e27, - collateral: 0, - deposit: 30_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: _i9_52, - lpBalance: 30_000 * 1e27, - depositTime: _startTime - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 0, + borrowerCollateral: 0, + borrowert0Np: 10.307611531622595991 * 1e18, + borrowerCollateralization: 1 * 1e18 + }); + _assertKicker({ + kicker: _lender, + claimable: 105.065056948053351817 * 1e18, + locked: 0 + }); + _assertBucket({ + index: _i9_91, + lpBalance: 0, // bucket is bankrupt + collateral: 0, + deposit: 0, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _lender, + index: _i9_91, + lpBalance: 0, // bucket is bankrupt + depositTime: _startTime + }); + _assertBucket({ + index: _i9_81, + lpBalance: 0, // bucket is bankrupt + collateral: 0, + deposit: 0, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _lender, + index: _i9_81, + lpBalance: 0, // bucket is bankrupt + depositTime: _startTime + }); + _assertBucket({ + index: _i9_72, + lpBalance: 11_000 * 1e18, + collateral: 0, + deposit: 8_935.875749431291350857 * 1e18, + exchangeRate: 0.812352340857390123 * 1e18 + }); + _assertLenderLpBalance({ + lender: _lender, + index: _i9_72, + lpBalance: 11_000 * 1e18, + depositTime: _startTime + }); + _assertBucket({ + index: _i9_62, + lpBalance: 25_000 * 1e18, + collateral: 0, + deposit: 25_000 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _lender, + index: _i9_62, + lpBalance: 25_000 * 1e18, + depositTime: _startTime + }); + _assertBucket({ + index: _i9_52, + lpBalance: 30_000 * 1e18, + collateral: 0, + deposit: 30_000 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _lender, + index: _i9_52, + lpBalance: 30_000 * 1e18, + depositTime: _startTime + }); vm.revertTo(postTakeSnapshot); - _assertReserveAuction( - { - reserves: 846.536571996419329799 * 1e18, - claimableReserves : 796.294450429437634598 * 1e18, - claimableReservesRemaining: 0, - auctionPrice: 0, - timeRemaining: 0 - } - ); + + _assertReserveAuction({ + reserves: 846.536571996419329799 * 1e18, + claimableReserves : 796.294450429437634598 * 1e18, + claimableReservesRemaining: 0, + auctionPrice: 0, + timeRemaining: 0 + }); + // partial clears / debt settled - max buckets to use is 1, remaining will be taken from reserves - _settle( - { - from: _lender, - borrower: _borrower2, - maxDepth: 1, - settledDebt: 2_923.975862386543877283 * 1e18 - } - ); - _assertReserveAuction( - { - reserves: 0.989870342666661239 * 1e18, - claimableReserves : 0, - claimableReservesRemaining: 0, - auctionPrice: 0, - timeRemaining: 0 - } - ); + _settle({ + from: _lender, + borrower: _borrower2, + maxDepth: 1, + settledDebt: 2_923.975862386543877283 * 1e18 + }); + + _assertReserveAuction({ + reserves: 0.989870342666661239 * 1e18, + claimableReserves : 0, + claimableReservesRemaining: 0, + auctionPrice: 0, + timeRemaining: 0 + }); _assertAuction( AuctionParams({ borrower: _borrower2, @@ -1878,31 +1608,27 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { neutralPrice: 10.449783245217816340 * 1e18 }) ); - _assertKicker( - { - kicker: _lender, - claimable: 0, - locked: 105.065056948053351817 * 1e18 // locked bond + reward, auction is not yet finalized - } - ); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 7_064.430823099934649143 * 1e18, - borrowerCollateral: 0, - borrowert0Np: 10.307611531622595991 * 1e18, - borrowerCollateralization: 0 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 7_064.430823099934649143 * 1e18, + borrowerCollateral: 0, + borrowert0Np: 10.307611531622595991 * 1e18, + borrowerCollateralization: 0 + }); + _assertKicker({ + kicker: _lender, + claimable: 0, + locked: 105.065056948053351817 * 1e18 // locked bond + reward, auction is not yet finalized + }); + // clear remaining debt - _settle( - { - from: _lender, - borrower: _borrower2, - maxDepth: 5, - settledDebt: 6_967.959658457733469639 * 1e18 - } - ); + _settle({ + from: _lender, + borrower: _borrower2, + maxDepth: 5, + settledDebt: 6_967.959658457733469639 * 1e18 + }); + _assertAuction( AuctionParams({ borrower: _borrower2, @@ -1919,61 +1645,55 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { neutralPrice: 0 }) ); - _assertKicker( - { - kicker: _lender, - claimable: 105.065056948053351817 * 1e18, - locked: 0 - } - ); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 0, - borrowerCollateral: 0, - borrowert0Np: 10.307611531622595991 * 1e18, - borrowerCollateralization: 1 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 0, + borrowerCollateral: 0, + borrowert0Np: 10.307611531622595991 * 1e18, + borrowerCollateralization: 1 * 1e18 + }); + _assertKicker({ + kicker: _lender, + claimable: 105.065056948053351817 * 1e18, + locked: 0 + }); // kicker withdraws his auction bonds assertEq(_quote.balanceOf(_lender), 46_248.354604754094247543 * 1e18); - _pool.withdrawBonds(); + + _pool.withdrawBonds(_lender); + assertEq(_quote.balanceOf(_lender), 46_353.419661702147599360 * 1e18); - _assertKicker( - { - kicker: _lender, - claimable: 0, - locked: 0 - } - ); + + _assertKicker({ + kicker: _lender, + claimable: 0, + locked: 0 + }); } function testTakeReverts() external tearDown { // Borrower2 borrows - _borrow( - { - from: _borrower2, - amount: 1_730 * 1e18, - indexLimit: _i9_72, - newLup: 9.721295865031779605 * 1e18 - } - ); + _borrow({ + from: _borrower2, + amount: 1_730 * 1e18, + indexLimit: _i9_72, + newLup: 9.721295865031779605 * 1e18 + }); // Skip to make borrower undercollateralized skip(100 days); - _kick( - { - from: _lender, - borrower: _borrower2, - debt: 9_976.561670003961916237 * 1e18, - collateral: 1_000 * 1e18, - bond: 98.533942419792216457 * 1e18, - transferAmount: 98.533942419792216457 * 1e18 - } - ); + _kick({ + from: _lender, + borrower: _borrower2, + debt: 9_976.561670003961916237 * 1e18, + collateral: 1_000 * 1e18, + bond: 98.533942419792216457 * 1e18, + transferAmount: 98.533942419792216457 * 1e18 + }); + _assertAuction( AuctionParams({ borrower: _borrower2, @@ -1990,31 +1710,25 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { neutralPrice: 10.449783245217816340 * 1e18 }) ); - _assertKicker( - { - kicker: _lender, - claimable: 0, - locked: 98.533942419792216457 * 1e18 - } - ); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 9_976.561670003961916237 * 1e18, - borrowerCollateral: 1_000 * 1e18, - borrowert0Np: 10.307611531622595991 * 1e18, - borrowerCollateralization: 0.974413448899967463 * 1e18 - } - ); - _assertReserveAuction( - { - reserves: 148.064352861909228810 * 1e18, - claimableReserves : 98.083873122003682866 * 1e18, - claimableReservesRemaining: 0, - auctionPrice: 0, - timeRemaining: 0 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 9_976.561670003961916237 * 1e18, + borrowerCollateral: 1_000 * 1e18, + borrowert0Np: 10.307611531622595991 * 1e18, + borrowerCollateralization: 0.974413448899967463 * 1e18 + }); + _assertKicker({ + kicker: _lender, + claimable: 0, + locked: 98.533942419792216457 * 1e18 + }); + _assertReserveAuction({ + reserves: 148.064352861909228810 * 1e18, + claimableReserves : 98.083873122003682866 * 1e18, + claimableReservesRemaining: 0, + auctionPrice: 0, + timeRemaining: 0 + }); // Skip to make borrower undercollateralized skip(100 days); @@ -2035,27 +1749,22 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { neutralPrice: 0 }) ); - - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 19.776602251620519294 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.115967548076923081 * 1e18, - borrowerCollateralization: 0.983110823724556080 * 1e18 - } - ); - - _kick( - { - from: _lender, - borrower: _borrower, - debt: 19.999089026951250136 * 1e18, - collateral: 2 * 1e18, - bond: 0.197766022516205193 * 1e18, - transferAmount: 0.197766022516205193 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 19.776602251620519294 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.115967548076923081 * 1e18, + borrowerCollateralization: 0.983110823724556080 * 1e18 + }); + + _kick({ + from: _lender, + borrower: _borrower, + debt: 19.999089026951250136 * 1e18, + collateral: 2 * 1e18, + bond: 0.197766022516205193 * 1e18, + transferAmount: 0.197766022516205193 * 1e18 + }); _assertAuction( AuctionParams({ @@ -2073,66 +1782,56 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { neutralPrice: 10.382716182100772629 * 1e18 }) ); - - _assertKicker( - { - kicker: _lender, - claimable: 0, - locked: 98.731708442308421650 * 1e18 - } - ); + _assertKicker({ + kicker: _lender, + claimable: 0, + locked: 98.731708442308421650 * 1e18 + }); skip(2 hours); // 10 borrowers draw debt to enable the min debt check for (uint i=0; i<10; ++i) { - _anonBorrowerDrawsDebt(1_000 * 1e18, 6_000 * 1e18, 7_777); - } + _anonBorrowerDrawsDebt(1_000 * 1e18, 6_000 * 1e18, MAX_FENWICK_INDEX); + } + // should revert if auction leaves borrower with debt under minimum pool debt - _assertTakeDebtUnderMinPoolDebtRevert( - { - from: _lender, - borrower: _borrower, - maxCollateral: 0.1 * 1e18 - } - ); + _assertTakeDebtUnderMinPoolDebtRevert({ + from: _lender, + borrower: _borrower, + maxCollateral: 0.1 * 1e18 + }); } function testTakeAuctionPriceLtNeutralPrice() external tearDown { - _addLiquidity( - { - from: _lender1, - amount: 1 * 1e18, - index: _i9_91, - lpAward: 1 * 1e27, - newLup: 9.721295865031779605 * 1e18 - } - ); + _addLiquidity({ + from: _lender1, + amount: 1 * 1e18, + index: _i9_91, + lpAward: 1 * 1e18, + newLup: 9.721295865031779605 * 1e18 + }); // Borrower2 borrows - _borrow( - { - from: _borrower2, - amount: 1_730 * 1e18, - indexLimit: _i9_72, - newLup: 9.721295865031779605 * 1e18 - } - ); + _borrow({ + from: _borrower2, + amount: 1_730 * 1e18, + indexLimit: _i9_72, + newLup: 9.721295865031779605 * 1e18 + }); // Skip to make borrower undercollateralized skip(100 days); - _kick( - { - from: _lender, - borrower: _borrower2, - debt: 9_976.561670003961916237 * 1e18, - collateral: 1_000 * 1e18, - bond: 98.533942419792216457 * 1e18, - transferAmount: 98.533942419792216457 * 1e18 - } - ); + _kick({ + from: _lender, + borrower: _borrower2, + debt: 9_976.561670003961916237 * 1e18, + collateral: 1_000 * 1e18, + bond: 98.533942419792216457 * 1e18, + transferAmount: 98.533942419792216457 * 1e18 + }); _assertAuction( AuctionParams({ @@ -2150,55 +1849,142 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { neutralPrice: 10.449783245217816340 * 1e18 }) ); - _assertKicker( - { - kicker: _lender, - claimable: 0, - locked: 98.533942419792216457 * 1e18 - } - ); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 9_976.561670003961916237 * 1e18, - borrowerCollateral: 1_000 * 1e18, - borrowert0Np: 10.307611531622595991 * 1e18, - borrowerCollateralization: 0.974413448899967463 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 9_976.561670003961916237 * 1e18, + borrowerCollateral: 1_000 * 1e18, + borrowert0Np: 10.307611531622595991 * 1e18, + borrowerCollateralization: 0.974413448899967463 * 1e18 + }); + _assertKicker({ + kicker: _lender, + claimable: 0, + locked: 98.533942419792216457 * 1e18 + }); skip(3 hours); - _assertBucket( - { - index: _i9_91, - lpBalance: 2_001 * 1e27, - collateral: 0, - deposit: 2_119.781255869507381179 * 1e18, - exchangeRate: 1.059360947461023179000000000 * 1e27 - } - ); + _assertBucket({ + index: _i9_91, + lpBalance: 2_001 * 1e18, + collateral: 0, + deposit: 2_119.781255869507381179 * 1e18, + exchangeRate: 1.059360947461023179 * 1e18 + }); + + _take({ + from: _lender, + borrower: _borrower2, + maxCollateral: 1_001 * 1e18, + bondChange: 98.533942419792216457 * 1e18, + givenAmount: 10_675.085498940513902727 * 1e18, + collateralTaken: 127.695058936100465256 * 1e18, + isReward: false + }); + + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 0, + borrowerCollateral: 872.304941063899534744 * 1e18, + borrowert0Np: 0, + borrowerCollateralization: 1 * 1e18 + }); + } +} - _take( - { - from: _lender, - borrower: _borrower2, - maxCollateral: 1_001 * 1e18, - bondChange: 98.533942419792216457 * 1e18, - givenAmount: 10_675.085498940513902727 * 1e18, - collateralTaken: 127.695058936100465256 * 1e18, - isReward: false - } - ); +contract ERC20PoolLiquidationsTakeAndRepayAllDebtInPoolTest is ERC20HelperContract { + + address internal _lender; + address internal _borrower; + address internal _kicker; + address internal _taker; + + function setUp() external { + _lender = makeAddr("lender"); + _borrower = makeAddr("borrower"); + _kicker = makeAddr("kicker"); + _taker = makeAddr("taker"); + + _mintQuoteAndApproveTokens(_lender, 1_000_000 * 1e18); + _mintQuoteAndApproveTokens(_borrower, 1_000_000 * 1e18); + _mintQuoteAndApproveTokens(_kicker, 1_000_000 * 1e18); + _mintQuoteAndApproveTokens(_taker, 1_000_000 * 1e18); + + _mintCollateralAndApproveTokens(_borrower, 150_000 * 1e18); + + _addInitialLiquidity({ + from: _lender, + amount: 1_000 * 1e18, + index: 2690 + }); + _addInitialLiquidity({ + from: _lender, + amount: 1_000 * 1e18, + index: 2700 + }); + } + + function testTakeAuctionRepaidAmountGreaterThanPoolDebt() external tearDown { + _repayDebtNoLupCheck({ + from: _borrower, + borrower: _borrower, + amountToRepay: 0, + amountRepaid: 0, + collateralToPull: 0 + }); + + _drawDebtNoLupCheck({ + from: _borrower, + borrower: _borrower, + amountToBorrow: 635.189921955815900534 * 1e18, + limitIndex: 7000, + collateralToPledge: 0.428329945169804100 * 1e18 + }); + + skip(3276); + + _repayDebtNoLupCheck({ + from: _borrower, + borrower: _borrower, + amountToRepay: type(uint256).max, + amountRepaid: 635.803983894118939950 * 1e18, + collateralToPull: 0.428329945169804100 * 1e18 + }); + + _drawDebtNoLupCheck({ + from: _borrower, + borrower: _borrower, + amountToBorrow: 100 * 1e18, + limitIndex: 7000, + collateralToPledge: 0.067433366047580170 * 1e18 + }); + + skip(964); + skip(86400 * 200); + + _kick({ + from: _kicker, + borrower: _borrower, + debt: 104.162540773774892915 * 1e18, + collateral: 0.067433366047580170 * 1e18, + bond: 1.028765834802714992 * 1e18, + transferAmount: 1.028765834802714992 * 1e18 + }); + + skip(964); + skip(3600 * 3); + + // the calculated repaid amount is with 1 WAD greater than the pool debt + // check that take works and doesn't overflow + _take({ + from: _taker, + borrower: _borrower, + maxCollateral: 0.067433366047580170 * 1e18, + bondChange: 1.028765834802714992 * 1e18, + givenAmount: 111.455789568155429076 * 1e18, + collateralTaken: 0.010471063560951988 * 1e18, + isReward: false + }); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 0, - borrowerCollateral: 872.304941063899534744 * 1e18, - borrowert0Np: 0, - borrowerCollateralization: 1 * 1e18 - } - ); } } diff --git a/tests/forge/ERC20Pool/ERC20PoolLoanHeap.t.sol b/tests/forge/ERC20Pool/ERC20PoolLoanHeap.t.sol new file mode 100644 index 000000000..c8fbbf988 --- /dev/null +++ b/tests/forge/ERC20Pool/ERC20PoolLoanHeap.t.sol @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.14; + +import { ERC20HelperContract } from './ERC20DSTestPlus.sol'; + +import 'src/libraries/helpers/PoolHelper.sol'; + +contract ERC20PoolLoanHeapTest is ERC20HelperContract { + + address internal _borrower1; + address internal _borrower2; + address internal _borrower3; + address internal _borrower4; + address internal _borrower5; + address internal _borrower6; + address internal _lender1; + address internal _lender2; + address internal _lender3; + address internal _lender4; + + function setUp() external { + _borrower1 = makeAddr("borrower1"); + _borrower2 = makeAddr("borrower2"); + _borrower3 = makeAddr("borrower3"); + _borrower4 = makeAddr("borrower4"); + _borrower5 = makeAddr("borrower5"); + _borrower6 = makeAddr("borrower6"); + _lender1 = makeAddr("lender1"); + _lender2 = makeAddr("lender2"); + _lender3 = makeAddr("lender3"); + _lender4 = makeAddr("lender4"); + + _mintQuoteAndApproveTokens(_lender1, 150_000 * 1e18); + _mintQuoteAndApproveTokens(_lender2, 150_000 * 1e18); + _mintQuoteAndApproveTokens(_lender3, 150_000 * 1e18); + _mintQuoteAndApproveTokens(_lender4, 5_000 * 1e18); + + _mintCollateralAndApproveTokens(_lender1, 1_000 * 1e18); + _mintCollateralAndApproveTokens(_lender3, 1_000 * 1e18); + _mintCollateralAndApproveTokens(_borrower1, 1_000 * 1e18); + _mintCollateralAndApproveTokens(_borrower2, 1_000 * 1e18); + _mintCollateralAndApproveTokens(_borrower3, 1_000 * 1e18); + _mintCollateralAndApproveTokens(_borrower4, 1_000 * 1e18); + _mintCollateralAndApproveTokens(_borrower5, 1_000 * 1e18); + _mintCollateralAndApproveTokens(_borrower6, 1_000 * 1e18); + + // Lender 1 adds Quote token accross 3 buckets + _addInitialLiquidity({ + from: _lender1, + amount: 50_000 * 1e18, + index: 2500 + }); + _addInitialLiquidity({ + from: _lender1, + amount: 50_000 * 1e18, + index: 2501 + }); + _addInitialLiquidity({ + from: _lender1, + amount: 1_000 * 1e18, + index: 2502 + }); + } + + function testLoanHeapUpdateThresholdPrice() external { + // all 6 borrowers draw debt from pool + _drawDebt({ + from: _borrower1, + borrower: _borrower1, + amountToBorrow: 1_000 * 1e18, + limitIndex: 5000, + collateralToPledge: 1_000 * 1e18, + newLup: 3_863.654368867279344664 * 1e18 + }); + _drawDebt({ + from: _borrower2, + borrower: _borrower2, + amountToBorrow: 2_000 * 1e18, + limitIndex: 5000, + collateralToPledge: 1_000 * 1e18, + newLup: 3_863.654368867279344664 * 1e18 + }); + _drawDebt({ + from: _borrower3, + borrower: _borrower3, + amountToBorrow: 3_000 * 1e18, + limitIndex: 5000, + collateralToPledge: 1_000 * 1e18, + newLup: 3_863.654368867279344664 * 1e18 + }); + _drawDebt({ + from: _borrower4, + borrower: _borrower4, + amountToBorrow: 4_000 * 1e18, + limitIndex: 5000, + collateralToPledge: 1_000 * 1e18, + newLup: 3_863.654368867279344664 * 1e18 + }); + _drawDebt({ + from: _borrower5, + borrower: _borrower5, + amountToBorrow: 5_000 * 1e18, + limitIndex: 5000, + collateralToPledge: 1_000 * 1e18, + newLup: 3_863.654368867279344664 * 1e18 + }); + _drawDebt({ + from: _borrower6, + borrower: _borrower6, + amountToBorrow: 6_000 * 1e18, + limitIndex: 5000, + collateralToPledge: 1_000 * 1e18, + newLup: 3_863.654368867279344664 * 1e18 + }); + + _assertLoans({ + noOfLoans: 6, + maxBorrower: _borrower6, + maxThresholdPrice: 6.005769230769230772 * 1e18 + }); + + // borrower 4 draws debt and becomes loan with highest threshold price in heap + _drawDebt({ + from: _borrower4, + borrower: _borrower4, + amountToBorrow: 10_000 * 1e18, + limitIndex: 5000, + collateralToPledge: 0, + newLup: 3_863.654368867279344664 * 1e18 + }); + + _assertLoans({ + noOfLoans: 6, + maxBorrower: _borrower4, + maxThresholdPrice: 14.013461538461538468 * 1e18 + }); + + // borrower 4 repays debt, borrower 6 becomes loan with highest threshold price in heap + _repayDebt({ + from: _borrower4, + borrower: _borrower4, + amountToRepay: 11_000 * 1e18, + amountRepaid: 11_000 * 1e18, + collateralToPull: 0, + newLup: 3_863.654368867279344664 * 1e18 + }); + + _assertLoans({ + noOfLoans: 6, + maxBorrower: _borrower6, + maxThresholdPrice: 6.005769230769230772 * 1e18 + }); + + // borrower 6 repays debt, borrower 5 becomes loan with highest threshold price in heap + _repayDebt({ + from: _borrower6, + borrower: _borrower6, + amountToRepay: 5_000 * 1e18, + amountRepaid: 5_000 * 1e18, + collateralToPull: 0, + newLup: 3_863.654368867279344664 * 1e18 + }); + + _assertLoans({ + noOfLoans: 6, + maxBorrower: _borrower5, + maxThresholdPrice: 5.004807692307692310 * 1e18 + }); + + // borrower 6 draws more debt and becomes loan with highest threshold price in heap + _drawDebt({ + from: _borrower6, + borrower: _borrower6, + amountToBorrow: 11_000 * 1e18, + limitIndex: 5000, + collateralToPledge: 0, + newLup: 3_863.654368867279344664 * 1e18 + }); + + _assertLoans({ + noOfLoans: 6, + maxBorrower: _borrower6, + maxThresholdPrice: 12.016346153846153854 * 1e18 + }); + } +} \ No newline at end of file diff --git a/tests/forge/ERC20Pool/ERC20PoolMulticall.t.sol b/tests/forge/ERC20Pool/ERC20PoolMulticall.t.sol index f4a61c935..26d758e5b 100644 --- a/tests/forge/ERC20Pool/ERC20PoolMulticall.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolMulticall.t.sol @@ -31,49 +31,49 @@ contract ERC20PoolMulticallTest is ERC20HelperContract { bytes[] memory callsToExecute = new bytes[](3); callsToExecute[0] = abi.encodeWithSignature( - "addQuoteToken(uint256,uint256)", + "addQuoteToken(uint256,uint256,uint256)", 10_000 * 1e18, - 2550 + 2550, + block.timestamp + 5 minutes ); callsToExecute[1] = abi.encodeWithSignature( - "addQuoteToken(uint256,uint256)", + "addQuoteToken(uint256,uint256,uint256)", 10_000 * 1e18, - 2551 + 2551, + block.timestamp + 5 minutes ); callsToExecute[2] = abi.encodeWithSignature( - "addQuoteToken(uint256,uint256)", + "addQuoteToken(uint256,uint256,uint256)", 10_000 * 1e18, - 2552 + 2552, + block.timestamp + 5 minutes ); changePrank(_lender); vm.expectEmit(true, true, false, true); - emit AddQuoteToken(_lender, 2550, 10_000 * 1e18, 10_000 * 1e27, MAX_PRICE); + emit AddQuoteToken(_lender, 2550, 10_000 * 1e18, 10_000 * 1e18, MAX_PRICE); vm.expectEmit(true, true, false, true); emit Transfer(_lender, address(_pool), 10_000 * 1e18); vm.expectEmit(true, true, false, true); - emit AddQuoteToken(_lender, 2551, 10_000 * 1e18, 10_000 * 1e27, MAX_PRICE); + emit AddQuoteToken(_lender, 2551, 10_000 * 1e18, 10_000 * 1e18, MAX_PRICE); vm.expectEmit(true, true, false, true); emit Transfer(_lender, address(_pool), 10_000 * 1e18); vm.expectEmit(true, true, false, true); - emit AddQuoteToken(_lender, 2552, 10_000 * 1e18, 10_000 * 1e27, MAX_PRICE); + emit AddQuoteToken(_lender, 2552, 10_000 * 1e18, 10_000 * 1e18, MAX_PRICE); vm.expectEmit(true, true, false, true); emit Transfer(_lender, address(_pool), 10_000 * 1e18); ERC20Pool(address(_pool)).multicall(callsToExecute); - - _assertPoolPrices( - { + _assertPoolPrices({ htp: 0, htpIndex: 7388, hpb: 3_010.892022197881557845 * 1e18, hpbIndex: 2550, lup: MAX_PRICE, lupIndex: 0 - } - ); + }); // check balances assertEq(_quote.balanceOf(address(_pool)), 30_000 * 1e18); @@ -82,59 +82,47 @@ contract ERC20PoolMulticallTest is ERC20HelperContract { assertEq(_pool.depositSize(), 30_000 * 1e18); // check buckets - _assertBucket( - { - index: 2550, - lpBalance: 10_000 * 1e27, - collateral: 0, - deposit: 10_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 2550, - lpBalance: 10_000 * 1e27, - depositTime: _startTime - } - ); - - _assertBucket( - { - index: 2551, - lpBalance: 10_000 * 1e27, - collateral: 0, - deposit: 10_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 2551, - lpBalance: 10_000 * 1e27, - depositTime: _startTime - } - ); - - _assertBucket( - { - index: 2552, - lpBalance: 10_000 * 1e27, - collateral: 0, - deposit: 10_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 2552, - lpBalance: 10_000 * 1e27, - depositTime: _startTime - } - ); + _assertBucket({ + index: 2550, + lpBalance: 10_000 * 1e18, + collateral: 0, + deposit: 10_000 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _lender, + index: 2550, + lpBalance: 10_000 * 1e18, + depositTime: _startTime + }); + + _assertBucket({ + index: 2551, + lpBalance: 10_000 * 1e18, + collateral: 0, + deposit: 10_000 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _lender, + index: 2551, + lpBalance: 10_000 * 1e18, + depositTime: _startTime + }); + + _assertBucket({ + index: 2552, + lpBalance: 10_000 * 1e18, + collateral: 0, + deposit: 10_000 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _lender, + index: 2552, + lpBalance: 10_000 * 1e18, + depositTime: _startTime + }); } function testMulticallRevertString() public { @@ -149,7 +137,7 @@ contract ERC20PoolMulticallTest is ERC20HelperContract { ); changePrank(_lender); - vm.expectRevert(IPoolErrors.LimitIndexReached.selector); + vm.expectRevert(IPoolErrors.LimitIndexExceeded.selector); ERC20Pool(address(_pool)).multicall(callsToExecute); } } diff --git a/tests/forge/ERC20Pool/ERC20PoolPrecision.t.sol b/tests/forge/ERC20Pool/ERC20PoolPrecision.t.sol index 8eb018c38..dbc6e5346 100644 --- a/tests/forge/ERC20Pool/ERC20PoolPrecision.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolPrecision.t.sol @@ -17,7 +17,7 @@ contract ERC20PoolPrecisionTest is ERC20DSTestPlus { uint256 internal constant MAX_DEPOSIT = 1e22 * 1e18; uint256 internal constant MAX_COLLATERAL = 1e12 * 1e18; uint256 internal constant POOL_PRECISION = 1e18; - uint256 internal constant LP_PRECISION = 1e27; + uint256 internal constant LP_PRECISION = 1e18; uint256 internal _collateralPrecision; uint256 internal _quotePrecision; @@ -91,125 +91,101 @@ contract ERC20PoolPrecisionTest is ERC20DSTestPlus { uint256 start = block.timestamp; // deposit 50_000 quote tokens into each of 3 buckets - _addInitialLiquidity( - { - from: _lender, - amount: 50_000 * POOL_PRECISION, - index: 2549 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 50_000 * POOL_PRECISION, - index: 2550 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 50_000 * POOL_PRECISION, - index: 2551 - } - ); + _addInitialLiquidity({ + from: _lender, + amount: 50_000 * POOL_PRECISION, + index: 2549 + }); + _addInitialLiquidity({ + from: _lender, + amount: 50_000 * POOL_PRECISION, + index: 2550 + }); + _addInitialLiquidity({ + from: _lender, + amount: 50_000 * POOL_PRECISION, + index: 2551 + }); // check balances assertEq(_quote.balanceOf(address(_pool)), 150_000 * _quotePrecision); assertEq(_quote.balanceOf(_lender), 50_000 * _quotePrecision); // check initial pool state - _assertPoolPrices( - { - htp: 0, - htpIndex: 7388, - hpb: 3_025.946482308870940904 * 1e18, - hpbIndex: 2549, - lup: MAX_PRICE, - lupIndex: 0 - } - ); - _assertLoans( - { - noOfLoans: 0, - maxBorrower: address(0), - maxThresholdPrice: 0 - } - ); + _assertPoolPrices({ + htp: 0, + htpIndex: 7388, + hpb: 3_025.946482308870940904 * 1e18, + hpbIndex: 2549, + lup: MAX_PRICE, + lupIndex: 0 + }); + _assertLoans({ + noOfLoans: 0, + maxBorrower: address(0), + maxThresholdPrice: 0 + }); assertEq(_pool.depositSize(), 150_000 * POOL_PRECISION); // check bucket balance - _assertBucket( - { - index: 2549, - lpBalance: 50_000 * 1e27, - collateral: 0, - deposit: 50_000 * POOL_PRECISION, - exchangeRate: 1 * LP_PRECISION - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 2549, - lpBalance: 50_000 * 1e27, - depositTime: start - } - ); + _assertBucket({ + index: 2549, + lpBalance: 50_000 * 1e18, + collateral: 0, + deposit: 50_000 * POOL_PRECISION, + exchangeRate: 1 * LP_PRECISION + }); + _assertLenderLpBalance({ + lender: _lender, + index: 2549, + lpBalance: 50_000 * 1e18, + depositTime: start + }); skip(1 days); // skip to avoid penalty // lender removes some quote token from highest priced bucket - _removeLiquidity( - { - from: _lender, - amount: 25_000 * POOL_PRECISION, - index: 2549, - newLup: MAX_PRICE, - lpRedeem: 25_000 * 1e27 - } - ); + _removeLiquidity({ + from: _lender, + amount: 25_000 * POOL_PRECISION, + index: 2549, + newLup: MAX_PRICE, + lpRedeem: 25_000 * 1e18 + }); // check balances assertEq(_quote.balanceOf(address(_pool)), 125_000 * _quotePrecision); assertEq(_quote.balanceOf(_lender), 75_000 * _quotePrecision); // check pool state - _assertPoolPrices( - { - htp: 0, - htpIndex: 7388, - hpb: 3_025.946482308870940904 * 1e18, - hpbIndex: 2549, - lup: MAX_PRICE, - lupIndex: 0 - } - ); - _assertLoans( - { - noOfLoans: 0, - maxBorrower: address(0), - maxThresholdPrice: 0 - } - ); + _assertPoolPrices({ + htp: 0, + htpIndex: 7388, + hpb: 3_025.946482308870940904 * 1e18, + hpbIndex: 2549, + lup: MAX_PRICE, + lupIndex: 0 + }); + _assertLoans({ + noOfLoans: 0, + maxBorrower: address(0), + maxThresholdPrice: 0 + }); assertEq(_pool.depositSize(), 125_000 * POOL_PRECISION); // check bucket balance - _assertBucket( - { - index: 2549, - lpBalance: 25_000 * 1e27, - collateral: 0, - deposit: 25_000 * POOL_PRECISION, - exchangeRate: 1 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 2549, - lpBalance: 25_000 * LP_PRECISION, - depositTime: start - } - ); + _assertBucket({ + index: 2549, + lpBalance: 25_000 * 1e18, + collateral: 0, + deposit: 25_000 * POOL_PRECISION, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _lender, + index: 2549, + lpBalance: 25_000 * LP_PRECISION, + depositTime: start + }); } function testAddRemoveCollateralPrecision ( @@ -266,36 +242,28 @@ contract ERC20PoolPrecisionTest is ERC20DSTestPlus { uint256 start = block.timestamp; - _addInitialLiquidity( - { - from: _lender, - amount: 50_000 * POOL_PRECISION, - index: 2549 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 50_000 * POOL_PRECISION, - index: 2550 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 50_000 * POOL_PRECISION, - index: 2551 - } - ); + _addInitialLiquidity({ + from: _lender, + amount: 50_000 * POOL_PRECISION, + index: 2549 + }); + _addInitialLiquidity({ + from: _lender, + amount: 50_000 * POOL_PRECISION, + index: 2550 + }); + _addInitialLiquidity({ + from: _lender, + amount: 50_000 * POOL_PRECISION, + index: 2551 + }); // borrowers adds collateral - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - amount: 50 * POOL_PRECISION - } - ); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + amount: 50 * POOL_PRECISION + }); // check balances assertEq(_collateral.balanceOf(address(_pool)), 50 * _collateralPrecision); @@ -304,55 +272,45 @@ contract ERC20PoolPrecisionTest is ERC20DSTestPlus { assertEq(_quote.balanceOf(_borrower), 0); // check pool state - _assertPoolPrices( - { - htp: 0, - htpIndex: 7388, - hpb: 3_025.946482308870940904 * 1e18, - hpbIndex: 2549, - lup: MAX_PRICE, - lupIndex: 0 - } - ); - _assertLoans( - { - noOfLoans: 0, - maxBorrower: address(0), - maxThresholdPrice: 0 - } - ); + _assertPoolPrices({ + htp: 0, + htpIndex: 7388, + hpb: 3_025.946482308870940904 * 1e18, + hpbIndex: 2549, + lup: MAX_PRICE, + lupIndex: 0 + }); + _assertLoans({ + noOfLoans: 0, + maxBorrower: address(0), + maxThresholdPrice: 0 + }); assertEq(_pool.depositSize(), 150_000 * POOL_PRECISION); // check bucket balance - _assertBucket( - { - index: 2549, - lpBalance: 50_000 * LP_PRECISION, - collateral: 0, - deposit: 50_000 * POOL_PRECISION, - exchangeRate: 1 * LP_PRECISION - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 2549, - lpBalance: 50_000 * LP_PRECISION, - depositTime: start - } - ); + _assertBucket({ + index: 2549, + lpBalance: 50_000 * LP_PRECISION, + collateral: 0, + deposit: 50_000 * POOL_PRECISION, + exchangeRate: 1 * LP_PRECISION + }); + _assertLenderLpBalance({ + lender: _lender, + index: 2549, + lpBalance: 50_000 * LP_PRECISION, + depositTime: start + }); // borrower borrows uint256 price = _priceAt(2549); - _borrow( - { - from: _borrower, - amount: 10_000 * POOL_PRECISION, - indexLimit: 3_000, - newLup: price - } - ); + _borrow({ + from: _borrower, + amount: 10_000 * POOL_PRECISION, + indexLimit: 3_000, + newLup: price + }); // check balances assertEq(_collateral.balanceOf(address(_pool)), 50 * _collateralPrecision); @@ -363,54 +321,47 @@ contract ERC20PoolPrecisionTest is ERC20DSTestPlus { // check pool state uint256 debt = 10_008.653846153846150000 * 1e18; uint256 col = 50 * 1e18; - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: debt, - borrowerCollateral: col, - borrowert0Np: 209.180865384615384535 * 1e18, - borrowerCollateralization: 15.116650694597107214 * 1e18 - } - ); - _assertPoolPrices( - { - htp: 200.173076923076923000 * 1e18, - htpIndex: 3093, - hpb: 3_025.946482308870940904 * 1e18, - hpbIndex: 2549, - lup: price, - lupIndex: 2549 - } - ); - _assertLoans( - { - noOfLoans: 1, - maxBorrower: _borrower, - maxThresholdPrice: 200.173076923076923000 * 1e18 - } - ); + + _assertBorrower({ + borrower: _borrower, + borrowerDebt: debt, + borrowerCollateral: col, + borrowert0Np: 209.180865384615384535 * 1e18, + borrowerCollateralization: 15.116650694597107214 * 1e18 + }); + _assertPoolPrices({ + htp: 200.173076923076923000 * 1e18, + htpIndex: 3093, + hpb: 3_025.946482308870940904 * 1e18, + hpbIndex: 2549, + lup: price, + lupIndex: 2549 + }); + _assertLoans({ + noOfLoans: 1, + maxBorrower: _borrower, + maxThresholdPrice: 200.173076923076923000 * 1e18 + }); + (uint256 poolDebt,,) = _pool.debtInfo(); + assertEq(_pool.depositSize(), 150_000 * POOL_PRECISION); assertEq(poolDebt, debt); assertEq(_pool.pledgedCollateral(), col); - _assertBucket( - { - index: 2549, - lpBalance: 50_000 * LP_PRECISION, - collateral: 0, - deposit: 50_000 * POOL_PRECISION, - exchangeRate: 1 * LP_PRECISION - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 2549, - lpBalance: 50_000 * LP_PRECISION, - depositTime: start - } - ); + _assertBucket({ + index: 2549, + lpBalance: 50_000 * LP_PRECISION, + collateral: 0, + deposit: 50_000 * POOL_PRECISION, + exchangeRate: 1 * LP_PRECISION + }); + _assertLenderLpBalance({ + lender: _lender, + index: 2549, + lpBalance: 50_000 * LP_PRECISION, + depositTime: start + }); // borrower repays half of loan _repayDebt({ @@ -432,54 +383,47 @@ contract ERC20PoolPrecisionTest is ERC20DSTestPlus { // check pool state debt = 5_008.653846153846150000 * 1e18; col = 50 * 1e18; - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: debt, - borrowerCollateral: col, - borrowert0Np: 209.180865384615384535 * 1e18, - borrowerCollateralization: 30.207183159927296805 * 1e18 - } - ); - _assertPoolPrices( - { - htp: 100.173076923076923000 * 1e18, - htpIndex: 3232, - hpb: 3_025.946482308870940904 * 1e18, - hpbIndex: 2549, - lup: price, - lupIndex: 2549 - } - ); - _assertLoans( - { - noOfLoans: 1, - maxBorrower: _borrower, - maxThresholdPrice: 100.173076923076923000 * 1e18 - } - ); + + _assertBorrower({ + borrower: _borrower, + borrowerDebt: debt, + borrowerCollateral: col, + borrowert0Np: 209.180865384615384535 * 1e18, + borrowerCollateralization: 30.207183159927296805 * 1e18 + }); + _assertPoolPrices({ + htp: 100.173076923076923000 * 1e18, + htpIndex: 3232, + hpb: 3_025.946482308870940904 * 1e18, + hpbIndex: 2549, + lup: price, + lupIndex: 2549 + }); + _assertLoans({ + noOfLoans: 1, + maxBorrower: _borrower, + maxThresholdPrice: 100.173076923076923000 * 1e18 + }); + (poolDebt,,) = _pool.debtInfo(); + assertEq(_pool.depositSize(), 150_000 * 1e18); assertEq(poolDebt, debt); assertEq(_pool.pledgedCollateral(), col); - _assertBucket( - { - index: 2549, - lpBalance: 50_000 * LP_PRECISION, - collateral: 0, - deposit: 50_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 2549, - lpBalance: 50_000 * LP_PRECISION, - depositTime: start - } - ); + _assertBucket({ + index: 2549, + lpBalance: 50_000 * LP_PRECISION, + collateral: 0, + deposit: 50_000 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _lender, + index: 2549, + lpBalance: 50_000 * LP_PRECISION, + depositTime: start + }); // remove all of the remaining claimable collateral uint256 unencumberedCollateral = col - _encumberedCollateral(debt, _lup()); @@ -536,7 +480,7 @@ contract ERC20PoolPrecisionTest is ERC20DSTestPlus { lpBalance: 0, collateral: 0, deposit: 0, - exchangeRate: 1e27 + exchangeRate: 1e18 }); // addQuoteToken should add scaled quote token amount validate LP @@ -545,8 +489,9 @@ contract ERC20PoolPrecisionTest is ERC20DSTestPlus { amount: quoteAmount, index: bucketId }); + (uint256 lenderLpBalance, ) = _pool.lenderInfo(bucketId, _lender); - assertEq(lenderLpBalance, scaledQuoteAmount * 1e9); + assertEq(lenderLpBalance, scaledQuoteAmount); // deposit collateral and sanity check bidder LPs uint256 bidderLpBalance; @@ -614,11 +559,12 @@ contract ERC20PoolPrecisionTest is ERC20DSTestPlus { amount: scaledQuoteAmount1, index: bucketId }); + (uint256 lpBalance1, ) = _pool.lenderInfo(bucketId, _lender); // addQuoteToken should add scaled quote token amount and LP vm.expectEmit(true, true, false, true); - emit AddQuoteToken(lender2, bucketId, scaledQuoteAmount2, scaledQuoteAmount2 * 1e9, MAX_PRICE); + emit AddQuoteToken(lender2, bucketId, scaledQuoteAmount2, scaledQuoteAmount2, MAX_PRICE); _addLiquidityNoEventCheck(lender2, quoteAmount2, bucketId); (uint256 lpBalance2, ) = _pool.lenderInfo(bucketId, lender2); if (scaledQuoteAmount2 != 0) { @@ -654,58 +600,53 @@ contract ERC20PoolPrecisionTest is ERC20DSTestPlus { uint256 amountToMove = bound(uint256(amountToMove_), 0, _lenderDepositNormalized); init(boundColPrecision, boundQuotePrecision); - _addInitialLiquidity( - { - from: _lender, - amount: _lenderDepositNormalized, - index: fromBucketId - } - ); + _addInitialLiquidity({ + from: _lender, + amount: _lenderDepositNormalized, + index: fromBucketId + }); if (fromBucketId == toBucketId) { - _assertMoveLiquidityToSamePriceRevert( - { - from: _lender, - amount: amountToMove, - fromIndex: fromBucketId, - toIndex: toBucketId - } - ); + _assertMoveLiquidityToSamePriceRevert({ + from: _lender, + amount: amountToMove, + fromIndex: fromBucketId, + toIndex: toBucketId + }); + return; } if (amountToMove != 0 && amountToMove < _quoteDust) { - _assertMoveLiquidityDustRevert( - { - from: _lender, - amount: amountToMove, - fromIndex: fromBucketId, - toIndex: toBucketId - } - ); + _assertMoveLiquidityDustRevert({ + from: _lender, + amount: amountToMove, + fromIndex: fromBucketId, + toIndex: toBucketId + }); + return; } - _moveLiquidity( - { - from: _lender, - amount: amountToMove, - fromIndex: fromBucketId, - toIndex: toBucketId, - lpRedeemFrom: amountToMove * 1e9, - lpAwardTo: amountToMove * 1e9, - newLup: MAX_PRICE - } - ); + _moveLiquidity({ + from: _lender, + amount: amountToMove, + fromIndex: fromBucketId, + toIndex: toBucketId, + lpRedeemFrom: amountToMove, + lpAwardTo: amountToMove, + newLup: MAX_PRICE + }); // validate from and to buckets have appropriate amounts of deposit and LPs (, uint256 deposit,, uint256 lps,,) = _poolUtils.bucketInfo(address(_pool), fromBucketId); uint256 remaining = _lenderDepositNormalized - amountToMove; + assertEq(deposit, remaining); - assertEq(lps, remaining * 1e9); + assertEq(lps, remaining); (, deposit,, lps,,) = _poolUtils.bucketInfo(address(_pool), toBucketId); assertEq(deposit, amountToMove); - assertEq(lps, amountToMove * 1e9); + assertEq(lps, amountToMove); } function testDrawMinDebtAmount( @@ -767,8 +708,10 @@ contract ERC20PoolPrecisionTest is ERC20DSTestPlus { // have last borrower attempt an bad repay before tearDown (uint256 minDebtAmount, , , ) = _poolUtils.poolUtilizationInfo(address(_pool)); assertGt(minDebtAmount, 1); + (uint256 debt, , ) = _poolUtils.borrowerInfo(address(_pool), borrower); uint256 repayAmount = debt - minDebtAmount / 2; + _assertRepayMinDebtRevert({ from: borrower, borrower: borrower, @@ -843,8 +786,10 @@ contract ERC20PoolPrecisionTest is ERC20DSTestPlus { collateralToPledge: collateralToPledge, newLup: _priceAt(bucketId) }); + (uint256 currentDebt, uint256 pledgedCollateral, ) = _poolUtils.borrowerInfo(address(_pool), _borrower); assertGt(currentDebt, debtToDraw); + // round the collateral amount to token precision uint256 collateralRounded = (collateralToPledge / collateralScale) * collateralScale; assertEq(pledgedCollateral, collateralRounded); @@ -853,6 +798,32 @@ contract ERC20PoolPrecisionTest is ERC20DSTestPlus { skip(1 weeks); } + function testFlashLoanPrecision( + uint8 collateralPrecisionDecimals_, + uint8 quotePrecisionDecimals_ + ) external tearDown { + // setup fuzzy bounds and initialize the pool + uint256 collateralDecimals = bound(uint256(collateralPrecisionDecimals_), 1, 18); + uint256 quoteDecimals = bound(uint256(quotePrecisionDecimals_), 1, 18); + init(collateralDecimals, quoteDecimals); + + // add liquidity + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2500 + }); + + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + amount: 150 * 1e18 + }); + + assertEq(_pool.maxFlashLoan(address(_collateral)), 150 * 10 ** collateralDecimals); + assertEq(_pool.maxFlashLoan(address(_quote)), 10_000 * 10 ** quoteDecimals); + } + /**********************/ /*** Helper Methods ***/ @@ -913,4 +884,24 @@ contract ERC20PoolPrecisionTest is ERC20DSTestPlus { newLup: MAX_PRICE }); } + + function testMoveQuoteDustAmountRevert() external virtual tearDown { + init(8, 6); + + _addInitialLiquidity({ + from: _lender, + amount: 50_000 * 1e6, + index: 2550 + }); + + assertEq(_quoteDust, 0.000001 * 1e18); + + _assertMoveLiquidityDustRevert({ + from: _lender, + amount: 0.00000001 * 1e18, + fromIndex: 2550, + toIndex: 2551 + }); + } + } \ No newline at end of file diff --git a/tests/forge/ERC20Pool/ERC20PoolPurchaseQuote.t.sol b/tests/forge/ERC20Pool/ERC20PoolPurchaseQuote.t.sol index a3e83fbe5..6806ef005 100644 --- a/tests/forge/ERC20Pool/ERC20PoolPurchaseQuote.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolPurchaseQuote.t.sol @@ -33,93 +33,78 @@ contract ERC20PoolPurchaseQuoteTokenTest is ERC20HelperContract { uint256 testIndex = 2550; // lender adds initial quote to pool - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: testIndex - } - ); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: testIndex + }); // bidder deposits collateral into a bucket uint256 collateralToPurchaseWith = 4 * 1e18; - _addCollateral( - { - from: _bidder, - amount: collateralToPurchaseWith, - index: testIndex, - lpAward: 12_043.56808879152623138 * 1e27 - } - ); + + _addCollateral({ + from: _bidder, + amount: collateralToPurchaseWith, + index: testIndex, + lpAward: 12_043.56808879152623138 * 1e18 + }); // check bucket state and LPs - _assertBucket( - { - index: testIndex, - lpBalance: 22_043.56808879152623138 * 1e27, - collateral: collateralToPurchaseWith, - deposit: 10_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: testIndex, - lpBalance: 10_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: _bidder, - index: testIndex, - lpBalance: 12_043.56808879152623138 * 1e27, - depositTime: _startTime - } - ); + _assertBucket({ + index: testIndex, + lpBalance: 22_043.56808879152623138 * 1e18, + collateral: collateralToPurchaseWith, + deposit: 10_000 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _lender, + index: testIndex, + lpBalance: 10_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: _bidder, + index: testIndex, + lpBalance: 12_043.56808879152623138 * 1e18, + depositTime: _startTime + }); uint256 availableCollateral = collateralToPurchaseWith; skip(1 days); // skip to avoid penalty + // bidder uses their LP to purchase all quote token in the bucket - _removeLiquidity( - { - from: _bidder, - amount: 10_000 * 1e18, - index: testIndex, - newLup: _lup(), - lpRedeem: 10_000 * 1e27 - } - ); + _removeLiquidity({ + from: _bidder, + amount: 10_000 * 1e18, + index: testIndex, + newLup: _lup(), + lpRedeem: 10_000 * 1e18 + }); + assertEq(_quote.balanceOf(_bidder), 10_000 * 1e18); // check bucket state - _assertBucket( - { - index: testIndex, - lpBalance: 12_043.56808879152623138 * 1e27, - collateral: collateralToPurchaseWith, - deposit: 0, - exchangeRate: 1 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: testIndex, - lpBalance: 10_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: _bidder, - index: testIndex, - lpBalance: 2_043.56808879152623138 * 1e27, - depositTime: _startTime - } - ); + _assertBucket({ + index: testIndex, + lpBalance: 12_043.56808879152623138 * 1e18, + collateral: collateralToPurchaseWith, + deposit: 0, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _lender, + index: testIndex, + lpBalance: 10_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: _bidder, + index: testIndex, + lpBalance: 2_043.56808879152623138 * 1e18, + depositTime: _startTime + }); // check pool state and balances assertEq(_collateral.balanceOf(_lender), 0); @@ -128,82 +113,67 @@ contract ERC20PoolPurchaseQuoteTokenTest is ERC20HelperContract { assertEq(_quote.balanceOf(address(_pool)), 0); // lender exchanges their LP for collateral - _removeAllCollateral( - { - from: _lender, - amount: 3.321274866808485288 * 1e18, - index: testIndex, - lpRedeem: 10_000 * 1e27 - } - ); - - _assertBucket( - { - index: testIndex, - lpBalance: 2_043.56808879152623138 * 1e27, - collateral: 0.678725133191514712 * 1e18, - deposit: 0, - exchangeRate: 0.999999999999999999892795209 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: testIndex, - lpBalance: 0, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: _bidder, - index: testIndex, - lpBalance: 2_043.56808879152623138 * 1e27, - depositTime: _startTime - } - ); + _removeAllCollateral({ + from: _lender, + amount: 3.321274866808485288 * 1e18, + index: testIndex, + lpRedeem: 10_000 * 1e18 + }); + + _assertBucket({ + index: testIndex, + lpBalance: 2_043.56808879152623138 * 1e18, + collateral: 0.678725133191514712 * 1e18, + deposit: 0, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _lender, + index: testIndex, + lpBalance: 0, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: _bidder, + index: testIndex, + lpBalance: 2_043.56808879152623138 * 1e18, + depositTime: _startTime + }); assertEq(_collateral.balanceOf(_lender), 3.321274866808485288 * 1e18); // bidder removes their _collateral - _removeAllCollateral( - { - from: _bidder, - amount: 0.678725133191514712 * 1e18, - index: testIndex, - lpRedeem: 2_043.56808879152623138 * 1e27 - } - ); + _removeAllCollateral({ + from: _bidder, + amount: 0.678725133191514712 * 1e18, + index: testIndex, + lpRedeem: 2_043.568088791526231161 * 1e18 + }); + // check pool balances assertEq(_collateral.balanceOf(address(_pool)), 0); assertEq(_quote.balanceOf(address(_pool)), 0); // check bucket state - _assertBucket( - { - index: testIndex, - lpBalance: 0, - collateral: 0, - deposit: 0, - exchangeRate: 1 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: testIndex, - lpBalance: 0, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: _bidder, - index: testIndex, - lpBalance: 0, - depositTime: _startTime - } - ); + _assertBucket({ + index: testIndex, + lpBalance: 0, + collateral: 0, + deposit: 0, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _lender, + index: testIndex, + lpBalance: 0, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: _bidder, + index: testIndex, + lpBalance: 0, + depositTime: _startTime + }); } /** @@ -214,62 +184,48 @@ contract ERC20PoolPurchaseQuoteTokenTest is ERC20HelperContract { // lenders add liquidity // lender 1 - _addInitialLiquidity( - { - from: _lender, - amount: 6_000 * 1e18, - index: 2550 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2551 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 5_000 * 1e18, - index: 2552 - } - ); + _addInitialLiquidity({ + from: _lender, + amount: 6_000 * 1e18, + index: 2550 + }); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2551 + }); + _addInitialLiquidity({ + from: _lender, + amount: 5_000 * 1e18, + index: 2552 + }); // lender 2 - _addInitialLiquidity( - { - from: _lender1, - amount: 4_000 * 1e18, - index: 2550 - } - ); - _addInitialLiquidity( - { - from: _lender1, - amount: 5_000 * 1e18, - index: 2552 - } - ); + _addInitialLiquidity({ + from: _lender1, + amount: 4_000 * 1e18, + index: 2550 + }); + _addInitialLiquidity({ + from: _lender1, + amount: 5_000 * 1e18, + index: 2552 + }); skip(3600); // borrower draws debt - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - amount: 100 * 1e18 - } - ); - _borrow( - { - from: _borrower, - amount: 15_000 * 1e18, - indexLimit: 3_000, - newLup: _priceAt(2551) - } - ); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + amount: 100 * 1e18 + }); + _borrow({ + from: _borrower, + amount: 15_000 * 1e18, + indexLimit: 3_000, + newLup: _priceAt(2551) + }); skip(86400); @@ -286,101 +242,88 @@ contract ERC20PoolPurchaseQuoteTokenTest is ERC20HelperContract { assertEq(collateralToPurchaseWith, 3.388032491631335842 * 1e18); // bidder purchases all quote from the highest bucket - _addCollateral( - { - from: _bidder, - amount: collateralToPurchaseWith, - index: 2550, - lpAward: 10_200.383861467480875668505869503 * 1e27 - } - ); + _addCollateral({ + from: _bidder, + amount: collateralToPurchaseWith, + index: 2550, + lpAward: 10_200.383861467480875669 * 1e18 + }); skip(25 hours); // remove liquidity after one day to avoid early withdraw penalty - _removeAllLiquidity( - { - from: _bidder, - amount: amountWithInterest, - index: 2550, - newLup: _priceAt(2552), - lpRedeem: 10_000.349513872212134187863727799 * 1e27 - } - ); + + _removeAllLiquidity({ + from: _bidder, + amount: amountWithInterest, + index: 2550, + newLup: _priceAt(2552), + lpRedeem: 10_000.349513872212134207 * 1e18 + }); // bidder withdraws unused collateral uint256 expectedCollateral = 0.066443194797165079 * 1e18; - _removeAllCollateral( - { - from: _bidder, - amount: expectedCollateral, - index: 2550, - lpRedeem: 200.034347595268741480642141704 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _bidder, - index: 2550, - lpBalance: 0, - depositTime: _startTime + 3600 + 86400 - } - ); + + _removeAllCollateral({ + from: _bidder, + amount: expectedCollateral, + index: 2550, + lpRedeem: 200.034347595268741462 * 1e18 + }); + + _assertLenderLpBalance({ + lender: _bidder, + index: 2550, + lpBalance: 0, + depositTime: _startTime + 3600 + 86400 + }); skip(7200); // lender exchanges their LP for collateral expectedCollateral = 1.992953578100502458 * 1e18; - _removeAllCollateral( - { - from: _lender, - amount: expectedCollateral, - index: 2550, - lpRedeem: 6_000 * 1e27 - } - ); - - _assertLenderLpBalance( - { - lender: _lender, - index: 2550, - lpBalance: 0, - depositTime: _startTime - } - ); + + _removeAllCollateral({ + from: _lender, + amount: expectedCollateral, + index: 2550, + lpRedeem: 6_000 * 1e18 + }); + + _assertLenderLpBalance({ + lender: _lender, + index: 2550, + lpBalance: 0, + depositTime: _startTime + }); skip(3600); // lender1 exchanges their LP for collateral expectedCollateral = 1.328635718733668305 * 1e18; - _removeAllCollateral( - { - from: _lender1, - amount: expectedCollateral, - index: 2550, - lpRedeem: 4_000 * 1e27 - } - ); - - _assertLenderLpBalance( - { - lender: _lender1, - index: 2550, - lpBalance: 0, - depositTime: _startTime - } - ); + + _removeAllCollateral({ + from: _lender1, + amount: expectedCollateral, + index: 2550, + lpRedeem: 4_000 * 1e18 + }); + + _assertLenderLpBalance({ + lender: _lender1, + index: 2550, + lpBalance: 0, + depositTime: _startTime + }); // check pool balances assertEq(_collateral.balanceOf(address(_pool)), 100 * 1e18); // check bucket state - _assertBucket( - { - index: 2550, - lpBalance: 0, - collateral: 0, - deposit: 0, - exchangeRate: 1 * 1e27 - } - ); + _assertBucket({ + index: 2550, + lpBalance: 0, + collateral: 0, + deposit: 0, + exchangeRate: 1 * 1e18 + }); } } diff --git a/tests/forge/ERC20Pool/ERC20PoolQuoteToken.t.sol b/tests/forge/ERC20Pool/ERC20PoolQuoteToken.t.sol index 3ccda4596..bd072cf69 100644 --- a/tests/forge/ERC20Pool/ERC20PoolQuoteToken.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolQuoteToken.t.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.14; import { ERC20HelperContract } from './ERC20DSTestPlus.sol'; +import 'src/interfaces/pool/IPool.sol'; import 'src/libraries/helpers/PoolHelper.sol'; contract ERC20PoolQuoteTokenTest is ERC20HelperContract { @@ -33,22 +34,13 @@ contract ERC20PoolQuoteTokenTest is ERC20HelperContract { function testPoolDepositQuoteToken() external tearDown { assertEq(_hpb(), MIN_PRICE); - // should revert if trying to deposit at index 0 - _assertAddLiquidityAtIndex0Revert( - { - from: _lender, - amount: 10_000 * 1e18 - } - ); - // test 10_000 deposit at price of 3_010.892022197881557845 - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2550 - } - ); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2550 + }); + _assertPool( PoolParams({ htp: 0, @@ -66,35 +58,31 @@ contract ERC20PoolQuoteTokenTest is ERC20HelperContract { interestRateUpdate: _startTime }) ); - _assertBucket( - { - index: 2550, - lpBalance: 10_000 * 1e27, - collateral: 0, - deposit: 10_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 2550, - lpBalance: 10_000 * 1e27, - depositTime: _startTime - } - ); + _assertBucket({ + index: 2550, + lpBalance: 10_000 * 1e18, + collateral: 0, + deposit: 10_000 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _lender, + index: 2550, + lpBalance: 10_000 * 1e18, + depositTime: _startTime + }); + // check balances assertEq(_quote.balanceOf(address(_pool)), 10_000 * 1e18); assertEq(_quote.balanceOf(_lender), 190_000 * 1e18); // test 20_000 deposit at price of 2_995.912459898389633881 - _addInitialLiquidity( - { - from: _lender, - amount: 20_000 * 1e18, - index: 2551 - } - ); + _addInitialLiquidity({ + from: _lender, + amount: 20_000 * 1e18, + index: 2551 + }); + _assertPool( PoolParams({ htp: 0, @@ -112,54 +100,44 @@ contract ERC20PoolQuoteTokenTest is ERC20HelperContract { interestRateUpdate: _startTime }) ); - - _assertBucket( - { - index: 2550, - lpBalance: 10_000 * 1e27, - collateral: 0, - deposit: 10_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 2550, - lpBalance: 10_000 * 1e27, - depositTime: _startTime - } - ); - _assertBucket( - { - index: 2551, - lpBalance: 20_000 * 1e27, - collateral: 0, - deposit: 20_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 2551, - lpBalance: 20_000 * 1e27, - depositTime: _startTime - } - ); + _assertBucket({ + index: 2550, + lpBalance: 10_000 * 1e18, + collateral: 0, + deposit: 10_000 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _lender, + index: 2550, + lpBalance: 10_000 * 1e18, + depositTime: _startTime + }); + _assertBucket({ + index: 2551, + lpBalance: 20_000 * 1e18, + collateral: 0, + deposit: 20_000 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _lender, + index: 2551, + lpBalance: 20_000 * 1e18, + depositTime: _startTime + }); // check balances assertEq(_quote.balanceOf(address(_pool)), 30_000 * 1e18); assertEq(_quote.balanceOf(_lender), 170_000 * 1e18); // test 40_000 deposit at price of 3_025.946482308870940904 DAI - _addInitialLiquidity( - { - from: _lender, - amount: 40_000 * 1e18, - index: 2549 - } - ); + _addInitialLiquidity({ + from: _lender, + amount: 40_000 * 1e18, + index: 2549 + }); + _assertPool( PoolParams({ htp: 0, @@ -177,92 +155,107 @@ contract ERC20PoolQuoteTokenTest is ERC20HelperContract { interestRateUpdate: _startTime }) ); - - _assertBucket( - { - index: 2549, - lpBalance: 40_000 * 1e27, - collateral: 0, - deposit: 40_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 2549, - lpBalance: 40_000 * 1e27, - depositTime: _startTime - } - ); - _assertBucket( - { - index: 2550, - lpBalance: 10_000 * 1e27, - collateral: 0, - deposit: 10_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 2550, - lpBalance: 10_000 * 1e27, - depositTime: _startTime - } - ); - _assertBucket( - { - index: 2551, - lpBalance: 20_000 * 1e27, - collateral: 0, - deposit: 20_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 2551, - lpBalance: 20_000 * 1e27, - depositTime: _startTime - } - ); + _assertBucket({ + index: 2549, + lpBalance: 40_000 * 1e18, + collateral: 0, + deposit: 40_000 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _lender, + index: 2549, + lpBalance: 40_000 * 1e18, + depositTime: _startTime + }); + _assertBucket({ + index: 2550, + lpBalance: 10_000 * 1e18, + collateral: 0, + deposit: 10_000 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _lender, + index: 2550, + lpBalance: 10_000 * 1e18, + depositTime: _startTime + }); + _assertBucket({ + index: 2551, + lpBalance: 20_000 * 1e18, + collateral: 0, + deposit: 20_000 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _lender, + index: 2551, + lpBalance: 20_000 * 1e18, + depositTime: _startTime + }); // check balances assertEq(_quote.balanceOf(address(_pool)), 70_000 * 1e18); assertEq(_quote.balanceOf(_lender), 130_000 * 1e18); } - function testPoolRemoveQuoteToken() external tearDown { - _addLiquidity( - { - from: _lender, - amount: 40_000 * 1e18, - index: 2549, - lpAward: 40_000 * 1e27, - newLup: MAX_PRICE - } - ); - _addLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2550, - lpAward: 10_000 * 1e27, - newLup: MAX_PRICE - } - ); - _addLiquidity( - { - from: _lender, - amount: 20_000 * 1e18, - index: 2551, - lpAward: 20_000 * 1e27, - newLup: MAX_PRICE - } + function testPoolAddQuoteTokenReverts() external tearDown { + // should revert if trying to deposit at index 0 + _assertAddLiquidityAtIndex0Revert({ + from: _lender, + amount: 10_000 * 1e18 + }); + + // should revert if passing an already-expired timestamp + _assertAddLiquidityExpiredRevert({ + from: _lender, + amount: 100_000 * 1e18, + index: 3232, + expiry: block.timestamp - 1 minutes + }); + + // should revert if passing future timestamp but time has elapsed + bytes memory data = abi.encodeWithSignature( + "addQuoteToken(uint256,uint256,uint256)", + 50_000 * 1e18, + 3333, + block.timestamp + 5 minutes ); + + // should succeed if time hasn't passed + (bool success, ) = address(_pool).call(data); + assertEq(success, true); + + // should fail if expiration exceeded + skip(6 minutes); + vm.expectRevert(IPoolErrors.TransactionExpired.selector); + (success, ) = address(_pool).call(data); + } + + function testPoolRemoveQuoteToken() external tearDown { + _addLiquidity({ + from: _lender, + amount: 40_000 * 1e18, + index: 2549, + lpAward: 40_000 * 1e18, + newLup: MAX_PRICE + }); + _addLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2550, + lpAward: 10_000 * 1e18, + newLup: MAX_PRICE + }); + _addLiquidity( { + from: _lender, + amount: 20_000 * 1e18, + index: 2551, + lpAward: 20_000 * 1e18, + newLup: MAX_PRICE + }); + _assertPool( PoolParams({ htp: 0, @@ -280,73 +273,60 @@ contract ERC20PoolQuoteTokenTest is ERC20HelperContract { interestRateUpdate: _startTime }) ); - - _assertBucket( - { - index: 2549, - lpBalance: 40_000 * 1e27, - collateral: 0, - deposit: 40_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 2549, - lpBalance: 40_000 * 1e27, - depositTime: _startTime - } - ); - _assertBucket( - { - index: 2550, - lpBalance: 10_000 * 1e27, - collateral: 0, - deposit: 10_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 2550, - lpBalance: 10_000 * 1e27, - depositTime: _startTime - } - ); - _assertBucket( - { - index: 2551, - lpBalance: 20_000 * 1e27, - collateral: 0, - deposit: 20_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 2551, - lpBalance: 20_000 * 1e27, - depositTime: _startTime - } - ); + _assertBucket({ + index: 2549, + lpBalance: 40_000 * 1e18, + collateral: 0, + deposit: 40_000 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _lender, + index: 2549, + lpBalance: 40_000 * 1e18, + depositTime: _startTime + }); + _assertBucket({ + index: 2550, + lpBalance: 10_000 * 1e18, + collateral: 0, + deposit: 10_000 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _lender, + index: 2550, + lpBalance: 10_000 * 1e18, + depositTime: _startTime + }); + _assertBucket({ + index: 2551, + lpBalance: 20_000 * 1e18, + collateral: 0, + deposit: 20_000 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _lender, + index: 2551, + lpBalance: 20_000 * 1e18, + depositTime: _startTime + }); // check balances assertEq(_quote.balanceOf(address(_pool)), 70_000 * 1e18); assertEq(_quote.balanceOf(_lender), 130_000 * 1e18); skip(1 days); // skip to avoid penalty - _removeLiquidity( - { - from: _lender, - amount: 5_000 * 1e18, - index: 2549, - newLup: MAX_PRICE, - lpRedeem: 5_000 * 1e27 - } - ); + + _removeLiquidity({ + from: _lender, + amount: 5_000 * 1e18, + index: 2549, + newLup: MAX_PRICE, + lpRedeem: 5_000 * 1e18 + }); + _assertPool( PoolParams({ htp: 0, @@ -364,72 +344,58 @@ contract ERC20PoolQuoteTokenTest is ERC20HelperContract { interestRateUpdate: _startTime + 1 days }) ); - - _assertBucket( - { - index: 2549, - lpBalance: 35_000 * 1e27, - collateral: 0, - deposit: 35_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 2549, - lpBalance: 35_000 * 1e27, - depositTime: _startTime - } - ); - _assertBucket( - { - index: 2550, - lpBalance: 10_000 * 1e27, - collateral: 0, - deposit: 10_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 2550, - lpBalance: 10_000 * 1e27, - depositTime: _startTime - } - ); - _assertBucket( - { - index: 2551, - lpBalance: 20_000 * 1e27, - collateral: 0, - deposit: 20_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 2551, - lpBalance: 20_000 * 1e27, - depositTime: _startTime - } - ); + _assertBucket({ + index: 2549, + lpBalance: 35_000 * 1e18, + collateral: 0, + deposit: 35_000 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _lender, + index: 2549, + lpBalance: 35_000 * 1e18, + depositTime: _startTime + }); + _assertBucket({ + index: 2550, + lpBalance: 10_000 * 1e18, + collateral: 0, + deposit: 10_000 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _lender, + index: 2550, + lpBalance: 10_000 * 1e18, + depositTime: _startTime + }); + _assertBucket({ + index: 2551, + lpBalance: 20_000 * 1e18, + collateral: 0, + deposit: 20_000 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _lender, + index: 2551, + lpBalance: 20_000 * 1e18, + depositTime: _startTime + }); // check balances assertEq(_quote.balanceOf(address(_pool)), 65_000 * 1e18); assertEq(_quote.balanceOf(_lender), 135_000 * 1e18); - _removeLiquidity( - { - from: _lender, - amount: 35_000 * 1e18, - index: 2549, - newLup: MAX_PRICE, - lpRedeem: 35_000 * 1e27 - } - ); + _removeLiquidity({ + from: _lender, + amount: 35_000 * 1e18, + index: 2549, + newLup: MAX_PRICE, + lpRedeem: 35_000 * 1e18 + }); + _assertPool( PoolParams({ htp: 0, @@ -447,58 +413,45 @@ contract ERC20PoolQuoteTokenTest is ERC20HelperContract { interestRateUpdate: _startTime + 1 days }) ); - - _assertBucket( - { - index: 2549, - lpBalance: 0, - collateral: 0, - deposit: 0, - exchangeRate: 1 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 2549, - lpBalance: 0, - depositTime: _startTime - } - ); - _assertBucket( - { - index: 2550, - lpBalance: 10_000 * 1e27, - collateral: 0, - deposit: 10_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 2550, - lpBalance: 10_000 * 1e27, - depositTime: _startTime - } - ); - _assertBucket( - { - index: 2551, - lpBalance: 20_000 * 1e27, - collateral: 0, - deposit: 20_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 2551, - lpBalance: 20_000 * 1e27, - depositTime: _startTime - } - ); + _assertBucket({ + index: 2549, + lpBalance: 0, + collateral: 0, + deposit: 0, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _lender, + index: 2549, + lpBalance: 0, + depositTime: _startTime + }); + _assertBucket({ + index: 2550, + lpBalance: 10_000 * 1e18, + collateral: 0, + deposit: 10_000 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _lender, + index: 2550, + lpBalance: 10_000 * 1e18, + depositTime: _startTime + }); + _assertBucket({ + index: 2551, + lpBalance: 20_000 * 1e18, + collateral: 0, + deposit: 20_000 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _lender, + index: 2551, + lpBalance: 20_000 * 1e18, + depositTime: _startTime + }); // check balances assertEq(_quote.balanceOf(address(_pool)), 30_000 * 1e18); @@ -512,32 +465,31 @@ contract ERC20PoolQuoteTokenTest is ERC20HelperContract { */ function testPoolRemoveQuoteTokenNotAvailable() external tearDown { _mintCollateralAndApproveTokens(_borrower, _collateral.balanceOf(_borrower) + 3_500_000 * 1e18); + // lender adds initial quote token - _addLiquidity( - { - from: _lender, - amount: 11_000 * 1e18, - index: 4550, - lpAward: 11_000 * 1e27, - newLup: MAX_PRICE - } - ); + _addLiquidity({ + from: _lender, + amount: 11_000 * 1e18, + index: 4550, + lpAward: 11_000 * 1e18, + newLup: MAX_PRICE + }); _drawDebt({ - from: _borrower, - borrower: _borrower, - amountToBorrow: 10_000 * 1e18, - limitIndex: 7000, + from: _borrower, + borrower: _borrower, + amountToBorrow: 10_000 * 1e18, + limitIndex: 7000, collateralToPledge: 3_500_000 * 1e18, - newLup: 0.140143083210662942 * 1e18 + newLup: 0.140143083210662942 * 1e18 }); _assertRemoveAllLiquidityLupBelowHtpRevert( - { - from: _lender, - index: 4550 - } - ); + { + from: _lender, + index: 4550 + } + ); } /** @@ -549,172 +501,147 @@ contract ERC20PoolQuoteTokenTest is ERC20HelperContract { function testPoolRemoveQuoteTokenReverts() external tearDown { _mintCollateralAndApproveTokens(_borrower, _collateral.balanceOf(_borrower) + 3_500_000 * 1e18); _mintCollateralAndApproveTokens(_lender, 1 * 1e18); + // lender adds initial quote token - _addLiquidity( - { - from: _lender, - amount: 41_000 * 1e18, - index: 4549, - lpAward: 41_000 * 1e27, - newLup: MAX_PRICE - } - ); - _addLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 4550, - lpAward: 10_000 * 1e27, - newLup: MAX_PRICE - } - ); - _addLiquidity( - { - from: _lender, - amount: 20_000 * 1e18, - index: 4551, - lpAward: 20_000 * 1e27, - newLup: MAX_PRICE - } - ); - _addLiquidity( - { - from: _lender, - amount: 30_000 * 1e18, - index: 4990, - lpAward: 30_000 * 1e27, - newLup: MAX_PRICE - } - ); + _addLiquidity({ + from: _lender, + amount: 41_000 * 1e18, + index: 4549, + lpAward: 41_000 * 1e18, + newLup: MAX_PRICE + }); + _addLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 4550, + lpAward: 10_000 * 1e18, + newLup: MAX_PRICE + }); + _addLiquidity({ + from: _lender, + amount: 20_000 * 1e18, + index: 4551, + lpAward: 20_000 * 1e18, + newLup: MAX_PRICE + }); + _addLiquidity( { + from: _lender, + amount: 30_000 * 1e18, + index: 4990, + lpAward: 30_000 * 1e18, + newLup: MAX_PRICE + }); + // add collateral in order to give lender LPs in bucket 5_000 with 0 deposit // used to test revert on remove when bucket deposit is 0 - _addCollateral( - { - from: _lender, - amount: 1 * 1e18, - index: 5000, - lpAward: 0.014854015662334135 * 1e27 - } - ); - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - amount: 3_500_000 * 1e18 - } - ); - _borrow( - { - from: _borrower, - amount: 70_000 * 1e18, - indexLimit: 7_000, - newLup: 0.139445853940958153 * 1e18 - } - ); + _addCollateral({ + from: _lender, + amount: 1 * 1e18, + index: 5000, + lpAward: 0.014854015662334135 * 1e18 + }); + + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + amount: 3_500_000 * 1e18 + }); + _borrow({ + from: _borrower, + amount: 70_000 * 1e18, + indexLimit: 7_000, + newLup: 0.139445853940958153 * 1e18 + }); // ensure lender cannot withdraw from a bucket with no deposit - _assertRemoveAllLiquidityNoClaimRevert( - { - from: _lender1, - index: 4550 - } - ); + _assertRemoveAllLiquidityNoClaimRevert({ + from: _lender1, + index: 4550 + }); + // should revert if no quote token in bucket deposit - _assertRemoveInsufficientLiquidityRevert( - { - from: _lender, - amount: 1 * 1e18, - index: 5000 - } - ); + _assertRemoveInsufficientLiquidityRevert({ + from: _lender, + amount: 1 * 1e18, + index: 5000 + }); + // should revert if removing quote token from higher price buckets would drive lup below htp - _assertRemoveLiquidityLupBelowHtpRevert( - { - from: _lender, - amount: 20_000 * 1e18, - index: 4551 - } - ); + _assertRemoveLiquidityLupBelowHtpRevert({ + from: _lender, + amount: 20_000 * 1e18, + index: 4551 + }); - _addLiquidity( - { - from: _lender1, - amount: 20_000 * 1e18, - index: 4550, - lpAward: 20_000 * 1e27, - newLup: _priceAt(4550) - } - ); + _addLiquidity({ + from: _lender1, + amount: 20_000 * 1e18, + index: 4550, + lpAward: 20_000 * 1e18, + newLup: _priceAt(4550) + }); skip(1 days); // skip to avoid penalty + // should be able to removeQuoteToken - _removeLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 4990, - newLup: _priceAt(4550), - lpRedeem: 10_000 * 1e27 - } - ); + _removeLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 4990, + newLup: _priceAt(4550), + lpRedeem: 10_000 * 1e18 + }); } function testPoolRemoveQuoteTokenWithCollateral() external { // add 10 collateral into the 100 bucket, for LP worth 1000 quote tokens _mintCollateralAndApproveTokens(_lender, 10 * 1e18); + uint256 i100 = _indexOf(100 * 1e18); - _addCollateral( - { - from: _lender, - amount: 10 * 1e18, - index: i100, - lpAward: 1003.3236814328200989 * 1e27 - } - ); - // someone else deposits into the bucket - _addLiquidity( - { - from: _lender1, - amount: 900 * 1e18, - index: i100, - lpAward: 900 * 1e27, - newLup: MAX_PRICE - } - ); + _addCollateral({ + from: _lender, + amount: 10 * 1e18, + index: i100, + lpAward: 1003.3236814328200989 * 1e18 + }); + + // another lender deposits into the bucket + _addLiquidity({ + from: _lender1, + amount: 900 * 1e18, + index: i100, + lpAward: 900 * 1e18, + newLup: MAX_PRICE + }); // should be able to remove a small amount of deposit skip(1 days); - _removeLiquidity( - { - from: _lender, - amount: 100 * 1e18, - index: i100, - newLup: MAX_PRICE, - lpRedeem: 100 * 1e27 - } - ); + + _removeLiquidity({ + from: _lender, + amount: 100 * 1e18, + index: i100, + newLup: MAX_PRICE, + lpRedeem: 100 * 1e18 + }); // should be able to remove the rest - _removeAllLiquidity( - { - from: _lender, - amount: 800 * 1e18, - index: i100, - newLup: MAX_PRICE, - lpRedeem: 800 * 1e27 - } - ); + _removeAllLiquidity({ + from: _lender, + amount: 800 * 1e18, + index: i100, + newLup: MAX_PRICE, + lpRedeem: 800 * 1e18 + }); - _assertBucket( - { - index: i100, - lpBalance: 1_003.3236814328200989 * 1e27, - collateral: 10 * 1e18, - deposit: 0, - exchangeRate: 1 * 1e27 - } - ); + _assertBucket({ + index: i100, + lpBalance: 1_003.3236814328200989 * 1e18, + collateral: 10 * 1e18, + deposit: 0, + exchangeRate: 1 * 1e18 + }); } function testPoolRemoveQuoteTokenWithDebt() external tearDown { @@ -723,59 +650,47 @@ contract ERC20PoolQuoteTokenTest is ERC20HelperContract { // lender adds initial quote token skip(1 minutes); // prevent deposit from having a zero timestamp - _addLiquidity( - { - from: _lender, - amount: 3_400 * 1e18, - index: 1606, - lpAward: 3_400 * 1e27, - newLup: MAX_PRICE - } - ); - _addLiquidity( - { - from: _lender, - amount: 3_400 * 1e18, - index: 1663, - lpAward: 3_400 * 1e27, - newLup: MAX_PRICE - } - ); + _addLiquidity({ + from: _lender, + amount: 3_400 * 1e18, + index: 1606, + lpAward: 3_400 * 1e18, + newLup: MAX_PRICE + }); + _addLiquidity({ + from: _lender, + amount: 3_400 * 1e18, + index: 1663, + lpAward: 3_400 * 1e18, + newLup: MAX_PRICE + }); - _assertBucket( - { - index: 1606, - lpBalance: 3_400 * 1e27, - collateral: 0, - deposit: 3_400 * 1e18, - exchangeRate: 1 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 1606, - lpBalance: 3_400 * 1e27, - depositTime: _startTime + 1 minutes - } - ); - _assertBucket( - { - index: 1663, - lpBalance: 3_400 * 1e27, - collateral: 0, - deposit: 3_400 * 1e18, - exchangeRate: 1 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 1663, - lpBalance: 3_400 * 1e27, - depositTime: _startTime + 1 minutes - } - ); + _assertBucket({ + index: 1606, + lpBalance: 3_400 * 1e18, + collateral: 0, + deposit: 3_400 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _lender, + index: 1606, + lpBalance: 3_400 * 1e18, + depositTime: _startTime + 1 minutes + }); + _assertBucket({ + index: 1663, + lpBalance: 3_400 * 1e18, + collateral: 0, + deposit: 3_400 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _lender, + index: 1663, + lpBalance: 3_400 * 1e18, + depositTime: _startTime + 1 minutes + }); skip(59 minutes); @@ -783,264 +698,218 @@ contract ERC20PoolQuoteTokenTest is ERC20HelperContract { // borrower takes a loan of 3000 quote token _drawDebt({ - from: _borrower, - borrower: _borrower, - amountToBorrow: 3_000 * 1e18, - limitIndex: 2_000, + from: _borrower, + borrower: _borrower, + amountToBorrow: 3_000 * 1e18, + limitIndex: 2_000, collateralToPledge: 100 * 1e18, - newLup: 333_777.824045947762079231 * 1e18 + newLup: 333_777.824045947762079231 * 1e18 }); skip(2 hours); - _assertLenderLpBalance( - { - lender: _lender, - index: 1663, - lpBalance: 3_400 * 1e27, - depositTime: _startTime + 1 minutes - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 1663, - lpBalance: 3_400 * 1e27, - depositTime: _startTime + 1 minutes - } - ); + _assertLenderLpBalance({ + lender: _lender, + index: 1663, + lpBalance: 3_400 * 1e18, + depositTime: _startTime + 1 minutes + }); + _assertLenderLpBalance({ + lender: _lender, + index: 1663, + lpBalance: 3_400 * 1e18, + depositTime: _startTime + 1 minutes + }); // lender makes a partial withdrawal, paying an early withdrawal penalty - current annualized interest rate divided by 52 (one week of interest) (uint256 interestRate, ) = _pool.interestRateInfo(); uint256 penalty = Maths.WAD - Maths.wdiv(interestRate, 52 * 10**18); assertLt(penalty, Maths.WAD); + uint256 expectedWithdrawal1 = Maths.wmul(1_700 * 1e18, penalty); - _removeLiquidityWithPenalty( - { - from: _lender, - amount: 1_700 * 1e18, - amountRemoved: expectedWithdrawal1, - index: 1606, - newLup: _priceAt(1663), - lpRedeem: 1_699.988795593461528952000000000 * 1e27 - } - ); + + _removeLiquidityWithPenalty({ + from: _lender, + amount: 1_700 * 1e18, + amountRemoved: expectedWithdrawal1, + index: 1606, + newLup: _priceAt(1663), + lpRedeem: 1_699.988795593461528952000000000 * 1e18 + }); // lender removes all quote token, including interest, from the bucket skip(1 days); + assertGt(_priceAt(1606), _htp()); + uint256 expectedWithdrawal2 = 1_700.144243451229452671 * 1e18; - _removeAllLiquidity( - { - from: _lender, - amount: expectedWithdrawal2, - index: 1606, - newLup: _priceAt(1663), - lpRedeem: 1_700.011204406538471048000000000 * 1e27 - } - ); + + _removeAllLiquidity({ + from: _lender, + amount: expectedWithdrawal2, + index: 1606, + newLup: _priceAt(1663), + lpRedeem: 1_700.011204406538471048000000000 * 1e18 + }); + assertEq(_quote.balanceOf(_lender), lenderBalanceBefore + expectedWithdrawal1 + expectedWithdrawal2); - _assertBucket( - { - index: 1606, - lpBalance: 0, - collateral: 0, - deposit: 0, - exchangeRate: 1 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 1606, - lpBalance: 0, - depositTime: _startTime + 1 minutes - } - ); - _assertBucket( - { - index: 1663, - lpBalance: 3_400 * 1e27, - collateral: 0, - deposit: 3_400.266076335718765800 * 1e18, - exchangeRate: 1.000078257745799637000000000 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 1663, - lpBalance: 3_400 * 1e27, - depositTime: _startTime + 1 minutes - } - ); + _assertBucket({ + index: 1606, + lpBalance: 0, + collateral: 0, + deposit: 0, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _lender, + index: 1606, + lpBalance: 0, + depositTime: _startTime + 1 minutes + }); + _assertBucket({ + index: 1663, + lpBalance: 3_400 * 1e18, + collateral: 0, + deposit: 3_400.266076335718765800 * 1e18, + exchangeRate: 1.000078257745799637000000000 * 1e18 + }); + _assertLenderLpBalance({ + lender: _lender, + index: 1663, + lpBalance: 3_400 * 1e18, + depositTime: _startTime + 1 minutes + }); } function testPoolMoveQuoteToken() external tearDown { - _addLiquidity( - { - from: _lender, - amount: 40_000 * 1e18, - index: 2549, - lpAward: 40_000 * 1e27, - newLup: MAX_PRICE - } - ); - _addLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2550, - lpAward: 10_000 * 1e27, - newLup: MAX_PRICE - } - ); - _addLiquidity( - { - from: _lender, - amount: 20_000 * 1e18, - index: 2551, - lpAward: 20_000 * 1e27, - newLup: MAX_PRICE - } - ); + _addLiquidity({ + from: _lender, + amount: 40_000 * 1e18, + index: 2549, + lpAward: 40_000 * 1e18, + newLup: MAX_PRICE + }); + _addLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2550, + lpAward: 10_000 * 1e18, + newLup: MAX_PRICE + }); + _addLiquidity( { + from: _lender, + amount: 20_000 * 1e18, + index: 2551, + lpAward: 20_000 * 1e18, + newLup: MAX_PRICE + }); - _assertLenderLpBalance( - { - lender: _lender, - index: 2549, - lpBalance: 40_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 2552, - lpBalance: 0, - depositTime: 0 - } - ); + _assertLenderLpBalance({ + lender: _lender, + index: 2549, + lpBalance: 40_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: _lender, + index: 2552, + lpBalance: 0, + depositTime: 0 + }); - _moveLiquidity( - { - from: _lender, - amount: 5_000 * 1e18, - fromIndex: 2549, - toIndex: 2552, - lpRedeemFrom: 5_000 * 1e27, - lpAwardTo: 5_000 * 1e27, - newLup: MAX_PRICE - } - ); + _moveLiquidity({ + from: _lender, + amount: 5_000 * 1e18, + fromIndex: 2549, + toIndex: 2552, + lpRedeemFrom: 5_000 * 1e18, + lpAwardTo: 5_000 * 1e18, + newLup: MAX_PRICE + }); - _assertLenderLpBalance( - { - lender: _lender, - index: 2549, - lpBalance: 35_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 2552, - lpBalance: 5_000 * 1e27, - depositTime: _startTime - } - ); + _assertLenderLpBalance({ + lender: _lender, + index: 2549, + lpBalance: 35_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: _lender, + index: 2552, + lpBalance: 5_000 * 1e18, + depositTime: _startTime + }); - _moveLiquidity( - { - from: _lender, - amount: 5_000 * 1e18, - fromIndex: 2549, - toIndex: 2540, - lpRedeemFrom: 5_000 * 1e27, - lpAwardTo: 5_000 * 1e27, - newLup: MAX_PRICE - } - ); + _moveLiquidity({ + from: _lender, + amount: 5_000 * 1e18, + fromIndex: 2549, + toIndex: 2540, + lpRedeemFrom: 5_000 * 1e18, + lpAwardTo: 5_000 * 1e18, + newLup: MAX_PRICE + }); - _assertLenderLpBalance( - { - lender: _lender, - index: 2540, - lpBalance: 5_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 2549, - lpBalance: 30_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 2552, - lpBalance: 5_000 * 1e27, - depositTime: _startTime - } - ); + _assertLenderLpBalance({ + lender: _lender, + index: 2540, + lpBalance: 5_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: _lender, + index: 2549, + lpBalance: 30_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: _lender, + index: 2552, + lpBalance: 5_000 * 1e18, + depositTime: _startTime + }); - _moveLiquidity( - { - from: _lender, - amount: 15_000 * 1e18, - fromIndex: 2551, - toIndex: 2777, - lpRedeemFrom: 15_000 * 1e27, - lpAwardTo: 15_000 * 1e27, - newLup: MAX_PRICE - } - ); + _moveLiquidity({ + from: _lender, + amount: 15_000 * 1e18, + fromIndex: 2551, + toIndex: 2777, + lpRedeemFrom: 15_000 * 1e18, + lpAwardTo: 15_000 * 1e18, + newLup: MAX_PRICE + }); - _assertLenderLpBalance( - { - lender: _lender, - index: 2540, - lpBalance: 5_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 2549, - lpBalance: 30_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 2551, - lpBalance: 5_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 2552, - lpBalance: 5_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 2777, - lpBalance: 15_000 * 1e27, - depositTime: _startTime - } - ); + _assertLenderLpBalance({ + lender: _lender, + index: 2540, + lpBalance: 5_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: _lender, + index: 2549, + lpBalance: 30_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: _lender, + index: 2551, + lpBalance: 5_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: _lender, + index: 2552, + lpBalance: 5_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: _lender, + index: 2777, + lpBalance: 15_000 * 1e18, + depositTime: _startTime + }); } /** @@ -1056,134 +925,118 @@ contract ERC20PoolQuoteTokenTest is ERC20HelperContract { _mintCollateralAndApproveTokens(_borrower, _collateral.balanceOf(_lender1) + 1_500_000 * 1e18); // lender adds initial quote token - _addLiquidity( - { - from: _lender, - amount: 40_000 * 1e18, - index: 4549, - lpAward: 40_000 * 1e27, - newLup: MAX_PRICE - } - ); - _addLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 4550, - lpAward: 10_000 * 1e27, - newLup: MAX_PRICE - } - ); - _addLiquidity( - { - from: _lender, - amount: 20_000 * 1e18, - index: 4551, - lpAward: 20_000 * 1e27, - newLup: MAX_PRICE - } - ); - _addLiquidity( - { - from: _lender, - amount: 30_000 * 1e18, - index: 4651, - lpAward: 30_000 * 1e27, - newLup: MAX_PRICE - } - ); + _addLiquidity({ + from: _lender, + amount: 40_000 * 1e18, + index: 4549, + lpAward: 40_000 * 1e18, + newLup: MAX_PRICE + }); + _addLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 4550, + lpAward: 10_000 * 1e18, + newLup: MAX_PRICE + }); + _addLiquidity({ + from: _lender, + amount: 20_000 * 1e18, + index: 4551, + lpAward: 20_000 * 1e18, + newLup: MAX_PRICE + }); + _addLiquidity({ + from: _lender, + amount: 30_000 * 1e18, + index: 4651, + lpAward: 30_000 * 1e18, + newLup: MAX_PRICE + }); // should revert if moving quote token to the existing price - _assertMoveLiquidityToSamePriceRevert( - { - from: _lender, - amount: 5_000 * 1e18, - fromIndex: 4549, - toIndex: 4549 - } - ); + _assertMoveLiquidityToSamePriceRevert({ + from: _lender, + amount: 5_000 * 1e18, + fromIndex: 4549, + toIndex: 4549 + }); // should revert if moving quote token to index 0 - _assertMoveLiquidityToIndex0Revert( - { - from: _lender, - amount: 5_000 * 1e18, - fromIndex: 4549 - } - ); + _assertMoveLiquidityToIndex0Revert({ + from: _lender, + amount: 5_000 * 1e18, + fromIndex: 4549 + }); // borrow all available quote in the higher priced original 3 buckets, as well as some of the new lowest price bucket - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - amount: 1_500_000 * 1e18 - } - ); - _borrow( - { - from: _borrower, - amount: 60_000.1 * 1e18, - indexLimit: 4_651, - newLup: 0.139445853940958153 * 1e18 - } - ); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + amount: 1_500_000 * 1e18 + }); + _borrow({ + from: _borrower, + amount: 60_000.1 * 1e18, + indexLimit: 4_651, + newLup: 0.139445853940958153 * 1e18 + }); // should revert if movement would drive lup below htp - _assertMoveLiquidityLupBelowHtpRevert( - { - from: _lender, - amount: 40_000 * 1e18, - fromIndex: 4549, - toIndex: 6000 - } - ); + _assertMoveLiquidityLupBelowHtpRevert({ + from: _lender, + amount: 40_000 * 1e18, + fromIndex: 4549, + toIndex: 6000 + }); + + // should revert if transaction expired + _assertMoveLiquidityExpiredRevert({ + from: _lender, + amount: 30_000 * 1e18, + fromIndex: 4549, + toIndex: 4459, + expiry: block.timestamp - 20 + }); // should be able to moveQuoteToken if properly specified - _moveLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - fromIndex: 4549, - toIndex: 4550, - lpRedeemFrom: 10_000 * 1e27, - lpAwardTo: 10_000 * 1e27, - newLup: _priceAt(4551) - } - ); + _moveLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + fromIndex: 4549, + toIndex: 4550, + lpRedeemFrom: 10_000 * 1e18, + lpAwardTo: 10_000 * 1e18, + newLup: _priceAt(4551) + }); } function testMoveQuoteTokenWithDebt() external tearDown { // lender makes an initial deposit skip(1 hours); - _addLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2873, - lpAward: 10_000 * 1e27, - newLup: MAX_PRICE - } - ); + _addLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2873, + lpAward: 10_000 * 1e18, + newLup: MAX_PRICE + }); // borrower draws debt, establishing a pool threshold price skip(2 hours); - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - amount: 10 * 1e18 - } - ); - _borrow( - { - from: _borrower, - amount: 5_000 * 1e18, - indexLimit: 3_000, - newLup: 601.252968524772188572 * 1e18 - } - ); + + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + amount: 10 * 1e18 + }); + _borrow({ + from: _borrower, + amount: 5_000 * 1e18, + indexLimit: 3_000, + newLup: 601.252968524772188572 * 1e18 + }); (uint256 poolDebt,,) = _pool.debtInfo(); uint256 ptp = Maths.wdiv(poolDebt, 10 * 1e18); @@ -1191,79 +1044,165 @@ contract ERC20PoolQuoteTokenTest is ERC20HelperContract { // lender moves some liquidity below the pool threshold price; penalty should be assessed skip(16 hours); - _moveLiquidityWithPenalty( - { - from: _lender, - amount: 2_500 * 1e18, - amountMoved: 2_497.596153846153845 * 1e18, - fromIndex: 2873, - toIndex: 2954, - lpRedeemFrom: 2_499.899333909953254268000000000 * 1e27, - lpAwardTo: 2_497.596153846153845 * 1e27, - newLup: _lup() - } - ); + + _moveLiquidityWithPenalty({ + from: _lender, + amount: 2_500 * 1e18, + amountMoved: 2_497.596153846153845 * 1e18, + fromIndex: 2873, + toIndex: 2954, + lpRedeemFrom: 2_499.899333909953254268000000000 * 1e18, + lpAwardTo: 2_497.596153846153845 * 1e18, + newLup: _lup() + }); // another lender provides liquidity to prevent LUP from moving skip(1 hours); - _addLiquidity( - { - from: _lender1, - amount: 1_000 * 1e18, - index: 2873, - lpAward: 999.956320611641422442838174928 * 1e27, - newLup: 601.252968524772188572 * 1e18 - } - ); + _addLiquidity({ + from: _lender1, + amount: 1_000 * 1e18, + index: 2873, + lpAward: 999.956320611641422443 * 1e18, + newLup: 601.252968524772188572 * 1e18 + }); // lender moves more liquidity; no penalty assessed as sufficient time has passed skip(12 hours); - _moveLiquidity( - { - from: _lender, - amount: 2_500 * 1e18, - fromIndex: 2873, - toIndex: 2954, - lpRedeemFrom: 2_499.810182702901761330952408614 * 1e27, - lpAwardTo: 2_500 * 1e27, - newLup: _lup() - } - ); + + _moveLiquidity({ + from: _lender, + amount: 2_500 * 1e18, + fromIndex: 2873, + toIndex: 2954, + lpRedeemFrom: 2_499.810182702901761331 * 1e18, + lpAwardTo: 2_500 * 1e18, + newLup: _lup() + }); // after a week, another lender funds the pool skip(7 days); - _addLiquidity( - { - from: _lender1, - amount: 9_000 * 1e18, - index: 2873, - lpAward: 8_993.373316759001213153971860794 * 1e27, - newLup: 601.252968524772188572 * 1e18 - } - ); + _addLiquidity({ + from: _lender1, + amount: 9_000 * 1e18, + index: 2873, + lpAward: 8_993.373316759001213155 * 1e18, + newLup: 601.252968524772188572 * 1e18 + }); // lender removes all their quote, with interest skip(1 hours); - _removeAllLiquidity( - { - from: _lender, - amount: 5_003.981613396490344248 * 1e18, - index: 2873, - newLup: 601.252968524772188572 * 1e18, - lpRedeem: 5_000.290483387144984401047591386 * 1e27 - } - ); - _removeAllLiquidity( - { - from: _lender, - amount: 4_997.596153846153845 * 1e18, - index: 2954, - newLup: 601.252968524772188572 * 1e18, - lpRedeem: 4_997.596153846153845 * 1e27 - } - ); + + _removeAllLiquidity({ + from: _lender, + amount: 5_003.981613396490344248 * 1e18, + index: 2873, + newLup: 601.252968524772188572 * 1e18, + lpRedeem: 5_000.290483387144984401 * 1e18 + }); + + _removeAllLiquidity({ + from: _lender, + amount: 4_997.596153846153845 * 1e18, + index: 2954, + newLup: 601.252968524772188572 * 1e18, + lpRedeem: 4_997.596153846153845 * 1e18 + }); + assertGt(_quote.balanceOf(_lender), 200_000 * 1e18); } + + function testAddRemoveQuoteTokenBucketExchangeRateInvariantDifferentActor() tearDown external { + _mintQuoteAndApproveTokens(_lender, 1000000000000000000 * 1e18); + + uint256 initialLenderBalance = _quote.balanceOf(_lender); + + _addCollateral({ + from: _borrower, + amount: 13167, + index: 2570, + lpAward: 35880690 + }); + + _assertLenderLpBalance({ + lender: _borrower, + index: 2570, + lpBalance: 35880690, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: _lender, + index: 2570, + lpBalance: 0, + depositTime: 0 + }); + _assertBucket({ + index: 2570, + lpBalance: 35880690, + collateral: 13167, + deposit: 0, + exchangeRate: 1 * 1e18 + }); + + _addLiquidity({ + from: _lender, + amount: 984665640564039457.584007913129639933 * 1e18, + index: 2570, + lpAward: 984665640564039457.584007913129639933 * 1e18, + newLup: MAX_PRICE + }); + + _assertLenderLpBalance({ + lender: _borrower, + index: 2570, + lpBalance: 35880690, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: _lender, + index: 2570, + lpBalance: 984665640564039457.584007913129639933 * 1e18, + depositTime: _startTime + }); + _assertBucket({ + index: 2570, + lpBalance: 984665640564039457.584007913165520623 * 1e18, + collateral: 13167, + deposit: 984665640564039457.584007913129639933 * 1e18, + exchangeRate: 1 * 1e18 // exchange rate should not change + }); + + skip(48 hours); // to avoid penalty + + _removeAllLiquidity({ + from: _lender, + amount: 984665640564039457.584007913129639933 * 1e18, + index: 2570, + newLup: MAX_PRICE, + lpRedeem: 984665640564039457.584007913129639933 * 1e18 + }); + + _assertLenderLpBalance({ + lender: _borrower, + index: 2570, + lpBalance: 35880690, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: _lender, + index: 2570, + lpBalance: 0, // LPs should get back to same value as before add / remove collateral + depositTime: _startTime + }); + _assertBucket({ + index: 2570, + lpBalance: 35880690, + collateral: 13167, + deposit: 0, + exchangeRate: 1 * 1e18 // exchange rate should not change + }); + + assertEq(_quote.balanceOf(_lender), initialLenderBalance); + } } diff --git a/tests/forge/ERC20Pool/ERC20PoolReserveAuction.t.sol b/tests/forge/ERC20Pool/ERC20PoolReserveAuction.t.sol new file mode 100644 index 000000000..b0b7fcd63 --- /dev/null +++ b/tests/forge/ERC20Pool/ERC20PoolReserveAuction.t.sol @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.14; + +import '@openzeppelin/contracts/token/ERC20/ERC20.sol'; + +import { ERC20HelperContract } from './ERC20DSTestPlus.sol'; +import { FlashloanBorrower, SomeDefiStrategy } from '../utils/FlashloanBorrower.sol'; + +import 'src/libraries/helpers/PoolHelper.sol'; +import 'src/ERC20Pool.sol'; +import 'src/ERC20PoolFactory.sol'; + +import { IPoolErrors } from 'src/interfaces/pool/IPool.sol'; + +contract ERC20PoolReserveAuctionTest is ERC20HelperContract { + + ERC20 WBTC = ERC20(0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599); + ERC20 USDC = ERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); + ERC20 AJNA = ERC20(_ajna); + + address internal _borrower; + address internal _lender; + address internal _bidder; + + function setUp() external { + _pool = ERC20Pool(new ERC20PoolFactory(address(AJNA)).deployPool(address(WBTC), address(USDC), 0.05 * 10**18)); + + _borrower = makeAddr("borrower"); + _lender = makeAddr("lender"); + _bidder = makeAddr("bidder"); + + deal(address(WBTC), _borrower, 10 * 1e8); + deal(address(USDC), _borrower, 100 * 1e6); + + deal(address(USDC), _lender, 10_000 * 1e6); + + deal(address(AJNA), _bidder, 10 * 1e18); + + vm.startPrank(_borrower); + WBTC.approve(address(_pool), 10 * 1e18); + USDC.approve(address(_pool), 1_000 * 1e18); + + changePrank(_bidder); + AJNA.approve(address(_pool), 10 * 1e18); + + changePrank(_lender); + USDC.approve(address(_pool), 1_000 * 1e18); + + _addInitialLiquidity({ + from: _lender, + amount: 1_000 * 1e18, + index: 2500 + }); + + _drawDebtNoLupCheck({ + from: _borrower, + borrower: _borrower, + amountToBorrow: 300 * 1e18, + limitIndex: 7000, + collateralToPledge: 1 * 1e18 + }); + } + + function testStartAndTakeUsdcReserveAuction() external { + // skip time to accumulate interest + skip(26 weeks); + + // repay entire debt + _repayDebtNoLupCheck({ + from: _borrower, + borrower: _borrower, + amountToRepay: 400 * 1e18, + amountRepaid: 307.869212479869665749 * 1e18, + collateralToPull: 0 + }); + + assertEq(USDC.balanceOf(address(_borrower)), 92.130788 * 1e6); + assertEq(USDC.balanceOf(address(_pool)), 1_007.869212 * 1e6); + + _assertReserveAuction({ + reserves: 1.297969216344413 * 1e18, + claimableReserves : 1.297969216344413 * 1e18, + claimableReservesRemaining: 0, + auctionPrice: 0, + timeRemaining: 0 + }); + + // kick off a new auction + _startClaimableReserveAuction({ + from: _bidder, + remainingReserves: 1.284989524180968870 * 1e18, + price: 1000000000 * 1e18 + }); + + skip(60 hours); + + _assertReserveAuction({ + reserves: 0.000000692163444130 * 1e18, + claimableReserves : 0.000000692163444130 * 1e18, + claimableReservesRemaining: 1.284989524180968870 * 1e18, + auctionPrice: 0.000000000867361737 * 1e18, + timeRemaining: 43200 + }); + + assertEq(USDC.balanceOf(address(_pool)), 1_007.856233 * 1e6); + assertEq(USDC.balanceOf(address(_bidder)), 0.012979 * 1e6); // kicker reward + assertEq(AJNA.balanceOf(address(_bidder)), 10 * 1e18); + + _pool.takeReserves(10 * 1e18); + + assertEq(USDC.balanceOf(address(_pool)), 1_006.571244 * 1e6); + assertEq(USDC.balanceOf(address(_bidder)), 1.297968 * 1e6); + assertEq(AJNA.balanceOf(address(_bidder)), 9.999999998885449254 * 1e18); + } +} \ No newline at end of file diff --git a/tests/forge/ERC20Pool/ERC20PoolTransferLPTokens.t.sol b/tests/forge/ERC20Pool/ERC20PoolTransferLPTokens.t.sol deleted file mode 100644 index 89dca6f78..000000000 --- a/tests/forge/ERC20Pool/ERC20PoolTransferLPTokens.t.sol +++ /dev/null @@ -1,632 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.14; - -import { ERC20HelperContract } from './ERC20DSTestPlus.sol'; - -import 'src/libraries/helpers/PoolHelper.sol'; - -contract ERC20PoolTransferLPTokensTest is ERC20HelperContract { - - address internal _lender; - address internal _lender1; - address internal _lender2; - - function setUp() external { - _lender = makeAddr("lender"); - _lender1 = makeAddr("lender1"); - _lender2 = makeAddr("lender2"); - - _mintQuoteAndApproveTokens(_lender, 200_000 * 1e18); - _mintQuoteAndApproveTokens(_lender1, 200_000 * 1e18); - _mintQuoteAndApproveTokens(_lender2, 200_000 * 1e18); - } - - /********************************/ - /*** Transfer LP Tokens Tests ***/ - /********************************/ - - function testTransferLPTokensToZeroAddress() external tearDown { - uint256[] memory indexes = new uint256[](3); - indexes[0] = 2550; - indexes[1] = 2551; - indexes[2] = 2552; - - // should fail if allowed owner is not set - _assertTransferNoAllowanceRevert( - { - operator: _lender, - from: _lender1, - to: _lender2, - indexes: indexes - } - ); - - // should fail if allowed owner is set to 0x - changePrank(_lender1); - _pool.approveLpOwnership(address(0), indexes[0], 1_000 * 1e18); - - _assertTransferNoAllowanceRevert( - { - operator: _lender, - from: _lender1, - to: _lender2, - indexes: indexes - } - ); - } - - function testTransferLPTokensToUnallowedAddress() external tearDown { - uint256[] memory indexes = new uint256[](3); - indexes[0] = 2550; - indexes[1] = 2551; - indexes[2] = 2552; - - // should fail if allowed owner is set to lender2 address but trying to transfer to lender address - changePrank(_lender1); - _pool.approveLpOwnership(_lender2, indexes[0], 1_000 * 1e27); - _pool.approveLpOwnership(_lender2, indexes[1], 1_000 * 1e27); - _pool.approveLpOwnership(_lender2, indexes[2], 1_000 * 1e27); - - _assertTransferNoAllowanceRevert( - { - operator: _lender, - from: _lender1, - to: _lender2, - indexes: indexes - } - ); - } - - function testTransferLPTokensToInvalidIndex() external tearDown { - uint256[] memory indexes = new uint256[](3); - indexes[0] = 9999; - indexes[1] = 2550; - indexes[2] = 2552; - - // should fail since 9999 is not a valid index - changePrank(_lender1); - _pool.approveLpOwnership(_lender2, indexes[0], 1_000 * 1e27); - _pool.approveLpOwnership(_lender2, indexes[1], 1_000 * 1e27); - _pool.approveLpOwnership(_lender2, indexes[2], 1_000 * 1e27); - - _assertTransferInvalidIndexRevert( - { - operator: _lender, - from: _lender1, - to: _lender2, - indexes: indexes - } - ); - } - - function testTransferLPTokensGreaterThanBalance() external tearDown { - uint256[] memory indexes = new uint256[](2); - indexes[0] = 2550; - indexes[1] = 2551; - - _addInitialLiquidity( - { - from: _lender1, - amount: 10_000 * 1e18, - index: indexes[0] - } - ); - _addInitialLiquidity( - { - from: _lender1, - amount: 20_000 * 1e18, - index: indexes[1] - } - ); - - // set allowed owner to lender2 address - _pool.approveLpOwnership(_lender2, indexes[0], 10_000 * 1e27); - _pool.approveLpOwnership(_lender2, indexes[1], 30_000 * 1e27); - - _assertTransferNoAllowanceRevert( - { - operator: _lender2, - from: _lender1, - to: _lender2, - indexes: indexes - } - ); - } - - function testTransferLPTokensForAllIndexes() external tearDown { - uint256[] memory indexes = new uint256[](3); - indexes[0] = 2550; - indexes[1] = 2551; - indexes[2] = 2552; - - skip(1 hours); - - _addInitialLiquidity( - { - from: _lender1, - amount: 10_000 * 1e18, - index: indexes[0] - } - ); - _addInitialLiquidity( - { - from: _lender1, - amount: 20_000 * 1e18, - index: indexes[1] - } - ); - _addInitialLiquidity( - { - from: _lender1, - amount: 30_000 * 1e18, - index: indexes[2] - } - ); - - // check lenders lp balance - _assertLenderLpBalance( - { - lender: _lender1, - index: indexes[0], - lpBalance: 10_000 * 1e27, - depositTime: _startTime + 1 hours - } - ); - _assertLenderLpBalance( - { - lender: _lender2, - index: indexes[0], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: _lender1, - index: indexes[1], - lpBalance: 20_000 * 1e27, - depositTime: _startTime + 1 hours - } - ); - _assertLenderLpBalance( - { - lender: _lender2, - index: indexes[1], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: _lender1, - index: indexes[2], - lpBalance: 30_000 * 1e27, - depositTime: _startTime + 1 hours - } - ); - _assertLenderLpBalance( - { - lender: _lender2, - index: indexes[2], - lpBalance: 0, - depositTime: 0 - } - ); - - // set allowed owner to lender2 address - _pool.approveLpOwnership(_lender2, indexes[0], 10_000 * 1e27); - _pool.approveLpOwnership(_lender2, indexes[1], 20_000 * 1e27); - _pool.approveLpOwnership(_lender2, indexes[2], 30_000 * 1e27); - - // transfer LP tokens for all indexes - _transferLpTokens( - { - operator: _lender, - from: _lender1, - to: _lender2, - indexes: indexes, - lpBalance: 60_000 * 1e27 - } - ); - - // check that old token ownership was removed - a new transfer should fail - _assertTransferNoAllowanceRevert( - { - operator: _lender, - from: _lender1, - to: _lender2, - indexes: indexes - } - ); - - // check lenders lp balance - _assertLenderLpBalance( - { - lender: _lender1, - index: indexes[0], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: _lender2, - index: indexes[0], - lpBalance: 10_000 * 1e27, - depositTime: _startTime + 1 hours - } - ); - _assertLenderLpBalance( - { - lender: _lender1, - index: indexes[1], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: _lender2, - index: indexes[1], - lpBalance: 20_000 * 1e27, - depositTime: _startTime + 1 hours - } - ); - _assertLenderLpBalance( - { - lender: _lender1, - index: indexes[2], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: _lender2, - index: indexes[2], - lpBalance: 30_000 * 1e27, - depositTime: _startTime + 1 hours - } - ); - } - - function testTransferLPTokensForTwoIndexes() external tearDown { - uint256[] memory depositIndexes = new uint256[](3); - depositIndexes[0] = 2550; - depositIndexes[1] = 2551; - depositIndexes[2] = 2552; - - uint256[] memory transferIndexes = new uint256[](2); - transferIndexes[0] = 2550; - transferIndexes[1] = 2552; - - _addInitialLiquidity( - { - from: _lender1, - amount: 10_000 * 1e18, - index: depositIndexes[0] - } - ); - _addInitialLiquidity( - { - from: _lender1, - amount: 20_000 * 1e18, - index: depositIndexes[1] - } - ); - _addInitialLiquidity( - { - from: _lender1, - amount: 30_000 * 1e18, - index: depositIndexes[2] - } - ); - - // check lenders lp balance - _assertLenderLpBalance( - { - lender: _lender1, - index: depositIndexes[0], - lpBalance: 10_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: _lender2, - index: depositIndexes[0], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: _lender1, - index: depositIndexes[1], - lpBalance: 20_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: _lender2, - index: depositIndexes[1], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: _lender1, - index: depositIndexes[2], - lpBalance: 30_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: _lender2, - index: depositIndexes[2], - lpBalance: 0, - depositTime: 0 - } - ); - - // set allowed owner to lender2 address - _pool.approveLpOwnership(_lender2, transferIndexes[0], 10_000 * 1e27); - _pool.approveLpOwnership(_lender2, transferIndexes[1], 30_000 * 1e27); - - // transfer LP tokens for 2 indexes - _transferLpTokens( - { - operator: _lender, - from: _lender1, - to: _lender2, - indexes: transferIndexes, - lpBalance: 40_000 * 1e27 - } - ); - - // check that old token ownership was removed - transfer with same indexes should fail - _assertTransferNoAllowanceRevert( - { - operator: _lender, - from: _lender1, - to: _lender2, - indexes: transferIndexes - } - ); - - // check lenders lp balance - _assertLenderLpBalance( - { - lender: _lender1, - index: depositIndexes[0], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: _lender2, - index: depositIndexes[0], - lpBalance: 10_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: _lender1, - index: depositIndexes[1], - lpBalance: 20_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: _lender2, - index: depositIndexes[1], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: _lender1, - index: depositIndexes[2], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: _lender2, - index: depositIndexes[2], - lpBalance: 30_000 * 1e27, - depositTime: _startTime - } - ); - } - - function testTransferLPTokensToLenderWithLPTokens() external tearDown { - uint256[] memory indexes = new uint256[](3); - indexes[0] = 2550; - indexes[1] = 2551; - indexes[2] = 2552; - - skip(1 hours); - - _addInitialLiquidity( - { - from: _lender1, - amount: 10_000 * 1e18, - index: indexes[0] - } - ); - _addInitialLiquidity( - { - from: _lender1, - amount: 20_000 * 1e18, - index: indexes[1] - } - ); - _addInitialLiquidity( - { - from: _lender1, - amount: 30_000 * 1e18, - index: indexes[2] - } - ); - - skip(1 hours); - - _addInitialLiquidity( - { - from: _lender2, - amount: 5_000 * 1e18, - index: indexes[0] - } - ); - _addInitialLiquidity( - { - from: _lender2, - amount: 10_000 * 1e18, - index: indexes[1] - } - ); - _addInitialLiquidity( - { - from: _lender2, - amount: 15_000 * 1e18, - index: indexes[2] - } - ); - - // check lenders lp balance - _assertLenderLpBalance( - { - lender: _lender1, - index: indexes[0], - lpBalance: 10_000 * 1e27, - depositTime: _startTime + 1 hours - } - ); - _assertLenderLpBalance( - { - lender: _lender2, - index: indexes[0], - lpBalance: 5_000 * 1e27, - depositTime: _startTime + 2 hours - } - ); - _assertLenderLpBalance( - { - lender: _lender1, - index: indexes[1], - lpBalance: 20_000 * 1e27, - depositTime: _startTime + 1 hours - } - ); - _assertLenderLpBalance( - { - lender: _lender2, - index: indexes[1], - lpBalance: 10_000 * 1e27, - depositTime: _startTime + 2 hours - } - ); - _assertLenderLpBalance( - { - lender: _lender1, - index: indexes[2], - lpBalance: 30_000 * 1e27, - depositTime: _startTime + 1 hours - } - ); - _assertLenderLpBalance( - { - lender: _lender2, - index: indexes[2], - lpBalance: 15_000 * 1e27, - depositTime: _startTime + 2 hours - } - ); - - // set allowed owner to lender2 address - changePrank(_lender1); - _pool.approveLpOwnership(_lender2, indexes[0], 10_000 * 1e27); - _pool.approveLpOwnership(_lender2, indexes[1], 20_000 * 1e27); - _pool.approveLpOwnership(_lender2, indexes[2], 30_000 * 1e27); - - // transfer LP tokens for all indexes - _transferLpTokens( - { - operator: _lender, - from: _lender1, - to: _lender2, - indexes: indexes, - lpBalance: 60_000 * 1e27 - } - ); - - // check that old token ownership was removed - transfer with same indexes should fail - _assertTransferNoAllowanceRevert( - { - operator: _lender, - from: _lender1, - to: _lender2, - indexes: indexes - } - ); - - // check lenders lp balance - _assertLenderLpBalance( - { - lender: _lender1, - index: indexes[0], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: _lender2, - index: indexes[0], - lpBalance: 15_000 * 1e27, - depositTime: _startTime + 2 hours - } - ); - _assertLenderLpBalance( - { - lender: _lender1, - index: indexes[1], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: _lender2, - index: indexes[1], - lpBalance: 30_000 * 1e27, - depositTime: _startTime + 2 hours - } - ); - _assertLenderLpBalance( - { - lender: _lender1, - index: indexes[2], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: _lender2, - index: indexes[2], - lpBalance: 45_000 * 1e27, - depositTime: _startTime + 2 hours - } - ); - } -} diff --git a/tests/forge/ERC20Pool/ERC20PoolTransferLPs.t.sol b/tests/forge/ERC20Pool/ERC20PoolTransferLPs.t.sol new file mode 100644 index 000000000..7ac64107d --- /dev/null +++ b/tests/forge/ERC20Pool/ERC20PoolTransferLPs.t.sol @@ -0,0 +1,541 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.14; + +import { ERC20HelperContract } from './ERC20DSTestPlus.sol'; + +import 'src/libraries/helpers/PoolHelper.sol'; + +contract ERC20PoolTransferLPsTest is ERC20HelperContract { + + address internal _lender; + address internal _lender1; + address internal _lender2; + + function setUp() external { + _lender = makeAddr("lender"); + _lender1 = makeAddr("lender1"); + _lender2 = makeAddr("lender2"); + + _mintQuoteAndApproveTokens(_lender, 200_000 * 1e18); + _mintQuoteAndApproveTokens(_lender1, 200_000 * 1e18); + _mintQuoteAndApproveTokens(_lender2, 200_000 * 1e18); + } + + /**************************/ + /*** Transfer LPs Tests ***/ + /**************************/ + + function testTransferLPsToZeroAddress() external tearDown { + uint256[] memory indexes = new uint256[](3); + indexes[0] = 2550; + indexes[1] = 2551; + indexes[2] = 2552; + + // should fail if allowed owner is not set + _assertTransferNoAllowanceRevert({ + operator: _lender, + from: _lender1, + to: _lender2, + indexes: indexes + }); + + // should fail if allowed owner is set to 0x + changePrank(_lender1); + _pool.approveLpOwnership(address(0), indexes[0], 1_000 * 1e18); + + _assertTransferNoAllowanceRevert({ + operator: _lender, + from: _lender1, + to: _lender2, + indexes: indexes + }); + } + + function testTransferLPsToUnallowedAddress() external tearDown { + uint256[] memory indexes = new uint256[](3); + indexes[0] = 2550; + indexes[1] = 2551; + indexes[2] = 2552; + + // should fail if allowed owner is set to lender2 address but trying to transfer to lender address + changePrank(_lender1); + _pool.approveLpOwnership(_lender2, indexes[0], 1_000 * 1e18); + _pool.approveLpOwnership(_lender2, indexes[1], 1_000 * 1e18); + _pool.approveLpOwnership(_lender2, indexes[2], 1_000 * 1e18); + + _assertTransferNoAllowanceRevert({ + operator: _lender, + from: _lender1, + to: _lender2, + indexes: indexes + }); + } + + function testTransferLPsToInvalidIndex() external tearDown { + uint256[] memory indexes = new uint256[](3); + indexes[0] = 9999; + indexes[1] = 2550; + indexes[2] = 2552; + + // should fail since 9999 is not a valid index + changePrank(_lender1); + _pool.approveLpOwnership(_lender2, indexes[0], 1_000 * 1e18); + _pool.approveLpOwnership(_lender2, indexes[1], 1_000 * 1e18); + _pool.approveLpOwnership(_lender2, indexes[2], 1_000 * 1e18); + + _assertTransferInvalidIndexRevert({ + operator: _lender, + from: _lender1, + to: _lender2, + indexes: indexes + }); + } + + function testTransferLPsGreaterThanBalance() external tearDown { + uint256[] memory indexes = new uint256[](2); + indexes[0] = 2550; + indexes[1] = 2551; + + _addInitialLiquidity({ + from: _lender1, + amount: 10_000 * 1e18, + index: indexes[0] + }); + _addInitialLiquidity({ + from: _lender1, + amount: 20_000 * 1e18, + index: indexes[1] + }); + + // set allowed owner to lender2 address + _pool.approveLpOwnership(_lender2, indexes[0], 10_000 * 1e18); + _pool.approveLpOwnership(_lender2, indexes[1], 30_000 * 1e18); + + _assertTransferNoAllowanceRevert({ + operator: _lender2, + from: _lender1, + to: _lender2, + indexes: indexes + }); + } + + function testTransferLPsToSameOwner() external { + uint256[] memory indexes = new uint256[](1); + indexes[0] = 2550; + + skip(1 hours); + + _addInitialLiquidity({ + from: _lender1, + amount: 10_000 * 1e18, + index: indexes[0] + }); + + changePrank(_lender1); + _pool.approveLpOwnership(_lender1, indexes[0], 10_000 * 1e18); + + _assertLenderLpBalance({ + lender: _lender1, + index: indexes[0], + lpBalance: 10_000 * 1e18, + depositTime: _startTime + 1 hours + }); + + // should revert if trying to transfer LPs to same address + _assertTransferToSameOwnerRevert({ + operator: _lender, + from: _lender1, + to: _lender1, + indexes: indexes + }); + } + + function testTransferLPsForAllIndexes() external tearDown { + uint256[] memory indexes = new uint256[](3); + indexes[0] = 2550; + indexes[1] = 2551; + indexes[2] = 2552; + + skip(1 hours); + + _addInitialLiquidity({ + from: _lender1, + amount: 10_000 * 1e18, + index: indexes[0] + }); + _addInitialLiquidity({ + from: _lender1, + amount: 20_000 * 1e18, + index: indexes[1] + }); + _addInitialLiquidity({ + from: _lender1, + amount: 30_000 * 1e18, + index: indexes[2] + }); + + // check lenders lp balance + _assertLenderLpBalance({ + lender: _lender1, + index: indexes[0], + lpBalance: 10_000 * 1e18, + depositTime: _startTime + 1 hours + }); + _assertLenderLpBalance({ + lender: _lender2, + index: indexes[0], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: _lender1, + index: indexes[1], + lpBalance: 20_000 * 1e18, + depositTime: _startTime + 1 hours + }); + _assertLenderLpBalance({ + lender: _lender2, + index: indexes[1], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: _lender1, + index: indexes[2], + lpBalance: 30_000 * 1e18, + depositTime: _startTime + 1 hours + }); + _assertLenderLpBalance({ + lender: _lender2, + index: indexes[2], + lpBalance: 0, + depositTime: 0 + }); + + // set allowed owner to lender2 address + _pool.approveLpOwnership(_lender2, indexes[0], 10_000 * 1e18); + _pool.approveLpOwnership(_lender2, indexes[1], 20_000 * 1e18); + _pool.approveLpOwnership(_lender2, indexes[2], 30_000 * 1e18); + + // transfer LP tokens for all indexes + _transferLPs({ + operator: _lender, + from: _lender1, + to: _lender2, + indexes: indexes, + lpBalance: 60_000 * 1e18 + }); + + // check that old token ownership was removed - a new transfer should fail + _assertTransferNoAllowanceRevert({ + operator: _lender, + from: _lender1, + to: _lender2, + indexes: indexes + }); + + // check lenders lp balance + _assertLenderLpBalance({ + lender: _lender1, + index: indexes[0], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: _lender2, + index: indexes[0], + lpBalance: 10_000 * 1e18, + depositTime: _startTime + 1 hours + }); + _assertLenderLpBalance({ + lender: _lender1, + index: indexes[1], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: _lender2, + index: indexes[1], + lpBalance: 20_000 * 1e18, + depositTime: _startTime + 1 hours + }); + _assertLenderLpBalance({ + lender: _lender1, + index: indexes[2], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: _lender2, + index: indexes[2], + lpBalance: 30_000 * 1e18, + depositTime: _startTime + 1 hours + }); + } + + function testTransferLPsForTwoIndexes() external tearDown { + uint256[] memory depositIndexes = new uint256[](3); + depositIndexes[0] = 2550; + depositIndexes[1] = 2551; + depositIndexes[2] = 2552; + + uint256[] memory transferIndexes = new uint256[](2); + transferIndexes[0] = 2550; + transferIndexes[1] = 2552; + + _addInitialLiquidity({ + from: _lender1, + amount: 10_000 * 1e18, + index: depositIndexes[0] + }); + _addInitialLiquidity({ + from: _lender1, + amount: 20_000 * 1e18, + index: depositIndexes[1] + }); + _addInitialLiquidity({ + from: _lender1, + amount: 30_000 * 1e18, + index: depositIndexes[2] + }); + + // check lenders lp balance + _assertLenderLpBalance({ + lender: _lender1, + index: depositIndexes[0], + lpBalance: 10_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: _lender2, + index: depositIndexes[0], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: _lender1, + index: depositIndexes[1], + lpBalance: 20_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: _lender2, + index: depositIndexes[1], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: _lender1, + index: depositIndexes[2], + lpBalance: 30_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: _lender2, + index: depositIndexes[2], + lpBalance: 0, + depositTime: 0 + }); + + // set allowed owner to lender2 address + _pool.approveLpOwnership(_lender2, transferIndexes[0], 10_000 * 1e18); + _pool.approveLpOwnership(_lender2, transferIndexes[1], 30_000 * 1e18); + + // transfer LP tokens for 2 indexes + _transferLPs({ + operator: _lender, + from: _lender1, + to: _lender2, + indexes: transferIndexes, + lpBalance: 40_000 * 1e18 + }); + + // check that old token ownership was removed - transfer with same indexes should fail + _assertTransferNoAllowanceRevert({ + operator: _lender, + from: _lender1, + to: _lender2, + indexes: transferIndexes + }); + + // check lenders lp balance + _assertLenderLpBalance({ + lender: _lender1, + index: depositIndexes[0], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: _lender2, + index: depositIndexes[0], + lpBalance: 10_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: _lender1, + index: depositIndexes[1], + lpBalance: 20_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: _lender2, + index: depositIndexes[1], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: _lender1, + index: depositIndexes[2], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: _lender2, + index: depositIndexes[2], + lpBalance: 30_000 * 1e18, + depositTime: _startTime + }); + } + + function testTransferLPsToLenderWithLPTokens() external tearDown { + uint256[] memory indexes = new uint256[](3); + indexes[0] = 2550; + indexes[1] = 2551; + indexes[2] = 2552; + + skip(1 hours); + + _addInitialLiquidity({ + from: _lender1, + amount: 10_000 * 1e18, + index: indexes[0] + }); + _addInitialLiquidity({ + from: _lender1, + amount: 20_000 * 1e18, + index: indexes[1] + }); + _addInitialLiquidity({ + from: _lender1, + amount: 30_000 * 1e18, + index: indexes[2] + }); + + skip(1 hours); + + _addInitialLiquidity({ + from: _lender2, + amount: 5_000 * 1e18, + index: indexes[0] + }); + _addInitialLiquidity({ + from: _lender2, + amount: 10_000 * 1e18, + index: indexes[1] + }); + _addInitialLiquidity({ + from: _lender2, + amount: 15_000 * 1e18, + index: indexes[2] + }); + + // check lenders lp balance + _assertLenderLpBalance({ + lender: _lender1, + index: indexes[0], + lpBalance: 10_000 * 1e18, + depositTime: _startTime + 1 hours + }); + _assertLenderLpBalance({ + lender: _lender2, + index: indexes[0], + lpBalance: 5_000 * 1e18, + depositTime: _startTime + 2 hours + }); + _assertLenderLpBalance({ + lender: _lender1, + index: indexes[1], + lpBalance: 20_000 * 1e18, + depositTime: _startTime + 1 hours + }); + _assertLenderLpBalance({ + lender: _lender2, + index: indexes[1], + lpBalance: 10_000 * 1e18, + depositTime: _startTime + 2 hours + }); + _assertLenderLpBalance({ + lender: _lender1, + index: indexes[2], + lpBalance: 30_000 * 1e18, + depositTime: _startTime + 1 hours + }); + _assertLenderLpBalance({ + lender: _lender2, + index: indexes[2], + lpBalance: 15_000 * 1e18, + depositTime: _startTime + 2 hours + }); + + // set allowed owner to lender2 address + changePrank(_lender1); + _pool.approveLpOwnership(_lender2, indexes[0], 10_000 * 1e18); + _pool.approveLpOwnership(_lender2, indexes[1], 20_000 * 1e18); + _pool.approveLpOwnership(_lender2, indexes[2], 30_000 * 1e18); + + // transfer LP tokens for all indexes + _transferLPs({ + operator: _lender, + from: _lender1, + to: _lender2, + indexes: indexes, + lpBalance: 60_000 * 1e18 + }); + + // check that old token ownership was removed - transfer with same indexes should fail + _assertTransferNoAllowanceRevert({ + operator: _lender, + from: _lender1, + to: _lender2, + indexes: indexes + }); + + // check lenders lp balance + _assertLenderLpBalance({ + lender: _lender1, + index: indexes[0], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: _lender2, + index: indexes[0], + lpBalance: 15_000 * 1e18, + depositTime: _startTime + 2 hours + }); + _assertLenderLpBalance({ + lender: _lender1, + index: indexes[1], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: _lender2, + index: indexes[1], + lpBalance: 30_000 * 1e18, + depositTime: _startTime + 2 hours + }); + _assertLenderLpBalance({ + lender: _lender1, + index: indexes[2], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: _lender2, + index: indexes[2], + lpBalance: 45_000 * 1e18, + depositTime: _startTime + 2 hours + }); + } +} diff --git a/tests/forge/ERC20Pool/ERC20SafeTransferTokens.sol b/tests/forge/ERC20Pool/ERC20SafeTransferTokens.sol index d72edb38e..06abe35ff 100644 --- a/tests/forge/ERC20Pool/ERC20SafeTransferTokens.sol +++ b/tests/forge/ERC20Pool/ERC20SafeTransferTokens.sol @@ -36,7 +36,7 @@ contract ERC20SafeTransferTokens is ERC20HelperContract { usdt.approve(address(pool), 1_000 * 1e18); - pool.addQuoteToken(1_000 * 1e18, 2549); + pool.addQuoteToken(1_000 * 1e18, 2549, type(uint256).max); changePrank(_borrower); @@ -54,7 +54,7 @@ contract ERC20SafeTransferTokens is ERC20HelperContract { _quote.approve(address(pool), 1_000 * 1e18); - pool.addQuoteToken(1_000 * 1e18, 2549); + pool.addQuoteToken(1_000 * 1e18, 2549, type(uint256).max); changePrank(_borrower); diff --git a/tests/forge/ERC20Pool/invariants/BasicInvariants.t.sol b/tests/forge/ERC20Pool/invariants/BasicInvariants.t.sol new file mode 100644 index 000000000..55062cabf --- /dev/null +++ b/tests/forge/ERC20Pool/invariants/BasicInvariants.t.sol @@ -0,0 +1,248 @@ + +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.14; + +import '@std/Test.sol'; +import "@std/console.sol"; + +import { TestBase } from './TestBase.sol'; + +import { Maths } from 'src/libraries/internal/Maths.sol'; + +import { LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX, BORROWER_MIN_BUCKET_INDEX, BasicPoolHandler } from './handlers/BasicPoolHandler.sol'; + +import { IBaseHandler } from './handlers/IBaseHandler.sol'; + +// contains invariants for the test +contract BasicInvariants is TestBase { + + /**************************************************************************************************************************************/ + /*** Invariant Tests ***/ + /*************************************************************************************************************************************** + * Bucket + * B1: totalBucketLPs === totalLenderLps + * B2: bucketLps == 0 (if bucket quote and collateral is 0) + * B3: exchangeRate == 0 (if bucket quote and collateral is 0) + * Quote Token + * QT1: poolQtBal + poolDebt >= totalBondEscrowed + poolDepositSize + * QT2: pool t0 debt = sum of all borrower's t0 debt + + * Collateral Token + * CT1: poolCtBal >= sum of all borrower's collateral + sum of all bucket's claimable collateral + * CT7: pool Pledged collateral = sum of all borrower's pledged collateral + + * Loan + * L1: for each Loan in loans array (LoansState.loans) starting from index 1, the corresponding address (Loan.borrower) is not 0x, the threshold price (Loan.thresholdPrice) is different than 0 + * L2: Loan in loans array (LoansState.loans) at index 0 has the corresponding address (Loan.borrower) equal with 0x address and the threshold price (Loan.thresholdPrice) equal with 0 + * L3: Loans array (LoansState.loans) is a max-heap with respect to t0-threshold price: the t0TP of loan at index i is >= the t0-threshold price of the loans at index 2i and 2i+1 + + * Interest Rate + * I1: Interest rate should only update once in 12 hours + * I3: Inflator should only update once per block + ****************************************************************************************************************************************/ + + uint256 internal constant NUM_ACTORS = 10; + BasicPoolHandler internal _basicPoolHandler; + address internal _handler; + + // bucket exchange rate tracking + mapping(uint256 => uint256) internal previousBucketExchangeRate; + + uint256 previousInterestRateUpdate; + + uint256 previousInflator; + + uint256 previousInflatorUpdate; + + function setUp() public override virtual{ + + super.setUp(); + + _basicPoolHandler = new BasicPoolHandler(address(_pool), address(_quote), address(_collateral), address(_poolInfo), NUM_ACTORS); + _handler = address(_basicPoolHandler); + excludeContract(address(_collateral)); + excludeContract(address(_quote)); + excludeContract(address(_poolFactory)); + excludeContract(address(_pool)); + excludeContract(address(_poolInfo)); + excludeContract(address(_impl)); + + for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { + ( , , , , ,uint256 exchangeRate) = _poolInfo.bucketInfo(address(_pool), bucketIndex); + previousBucketExchangeRate[bucketIndex] = exchangeRate; + } + + (, previousInterestRateUpdate) = _pool.interestRateInfo(); + + // TODO: Change once this issue is resolved -> https://github.com/foundry-rs/foundry/issues/2963 + targetSender(address(0x1234)); + } + + // checks pool lps are equal to sum of all lender lps in a bucket + function invariant_Lps_B1() public { + uint256 actorCount = IBaseHandler(_handler).getActorsCount(); + for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { + uint256 totalLps; + for (uint256 i = 0; i < actorCount; i++) { + address lender = IBaseHandler(_handler).actors(i); + (uint256 lps, ) = _pool.lenderInfo(bucketIndex, lender); + totalLps += lps; + } + (uint256 bucketLps, , , , ) = _pool.bucketInfo(bucketIndex); + assertEq(bucketLps, totalLps, "Incorrect Bucket/lender lps"); + } + } + + // checks bucket lps are equal to 0 if bucket quote and collateral are 0 + // checks exchange rate is 1e27 if bucket quote and collateral are 0 + function invariant_Buckets_B2_B3() public view { + for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { + ( ,uint256 deposit, uint256 collateral, uint256 bucketLps, ,uint256 exchangeRate) = _poolInfo.bucketInfo(address(_pool), bucketIndex); + + if (collateral == 0 && deposit == 0) { + require(bucketLps == 0, "Incorrect bucket lps"); + require(exchangeRate == 1e18, "Incorrect exchange rate"); + } + } + } + + // checks pool quote token balance is greater than equals total deposits in pool + function invariant_quoteTokenBalance_QT1() public { + uint256 poolBalance = _quote.balanceOf(address(_pool)); + uint256 t0debt = _pool.totalT0Debt(); + (uint256 inflator, ) = _pool.inflatorInfo(); + uint256 poolDebt = Maths.wmul(t0debt, inflator); + (uint256 totalPoolBond, uint256 unClaimed, ) = _pool.reservesInfo(); + + assertGe(poolBalance + poolDebt, totalPoolBond + _pool.depositSize() + unClaimed, "Incorrect pool debt"); + } + + // checks pools collateral Balance to be equal to collateral pledged + function invariant_collateralBalance_CT1_CT7() public { + uint256 actorCount = IBaseHandler(_handler).getActorsCount(); + uint256 totalCollateralPledged; + for(uint256 i = 0; i < actorCount; i++) { + address borrower = IBaseHandler(_handler).actors(i); + ( , uint256 borrowerCollateral, ) = _pool.borrowerInfo(borrower); + totalCollateralPledged += borrowerCollateral; + } + + assertEq(_pool.pledgedCollateral(), totalCollateralPledged, "Incorrect Collateral Pledged"); + + uint256 collateralBalance = _collateral.balanceOf(address(_pool)); + uint256 bucketCollateral; + + for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { + (, , uint256 collateral , , ) = _pool.bucketInfo(bucketIndex); + bucketCollateral += collateral; + } + + assertGe(collateralBalance, bucketCollateral + _pool.pledgedCollateral()); + } + + // checks pool debt is equal to sum of all borrowers debt + function invariant_pooldebt_QT2() public view { + uint256 actorCount = IBaseHandler(_handler).getActorsCount(); + uint256 totalDebt; + for(uint256 i = 0; i < actorCount; i++) { + address borrower = IBaseHandler(_handler).actors(i); + (uint256 debt, , ) = _pool.borrowerInfo(borrower); + totalDebt += debt; + } + + uint256 poolDebt = _pool.totalT0Debt(); + + require(poolDebt == totalDebt, "Incorrect pool debt"); + } + + function _invariant_exchangeRate_RE1_RE2_R3_R4_R5_R6() public { + for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { + ( , , , , ,uint256 exchangeRate) = _poolInfo.bucketInfo(address(_pool), bucketIndex); + if (!IBaseHandler(_handler).shouldExchangeRateChange()) { + console.log("======================================"); + console.log("Bucket Index -->", bucketIndex); + console.log("Previous exchange Rate -->", previousBucketExchangeRate[bucketIndex]); + console.log("Current exchange Rate -->", exchangeRate); + requireWithinDiff(exchangeRate, previousBucketExchangeRate[bucketIndex], 1e12, "Incorrect exchange Rate changed"); + console.log("======================================"); + } + previousBucketExchangeRate[bucketIndex] = exchangeRate; + } + } + + function invariant_loan_L1_L2_L3() public view { + (address borrower, uint256 tp) = _pool.loanInfo(0); + + // first loan in loan heap should be 0 + require(borrower == address(0), "Incorrect borrower"); + require(tp == 0, "Incorrect threshold price"); + + ( , , uint256 totalLoans) = _pool.loansInfo(); + + for(uint256 loanId = 1; loanId < totalLoans; loanId++) { + (borrower, tp) = _pool.loanInfo(loanId); + + // borrower address and threshold price should not 0 + require(borrower != address(0), "Incorrect borrower"); + require(tp != 0, "Incorrect threshold price"); + + // tp of a loan at index 'i' in loan array should be greater than equals to loans at index '2i' and '2i+1' + (, uint256 tp1) = _pool.loanInfo(2 * loanId); + (, uint256 tp2) = _pool.loanInfo(2 * loanId + 1); + + require(tp >= tp1, "Incorrect loan heap"); + require(tp >= tp2, "Incorrect loan heap"); + } + } + + // interest should only update once in 12 hours + function invariant_interest_rate_I1() public { + + (, uint256 currentInterestRateUpdate) = _pool.interestRateInfo(); + + if (currentInterestRateUpdate != previousInterestRateUpdate) { + require(currentInterestRateUpdate - previousInterestRateUpdate >= 12 hours, "Incorrect interest rate update"); + } + previousInterestRateUpdate = currentInterestRateUpdate; + } + + // inflator should only update once per block + function invariant_inflator_I3() public { + (uint256 currentInflator, uint256 currentInflatorUpdate) = _pool.inflatorInfo(); + if(currentInflatorUpdate == previousInflatorUpdate) { + require(currentInflator == previousInflator, "Incorrect inflator update"); + } + previousInflator = currentInflator; + previousInflatorUpdate = currentInflatorUpdate; + } + + function invariant_call_summary() external view virtual { + console.log("\nCall Summary\n"); + console.log("--Lender----------"); + console.log("BBasicHandler.addQuoteToken ", IBaseHandler(_handler).numberOfCalls("BBasicHandler.addQuoteToken")); + console.log("UBBasicHandler.addQuoteToken ", IBaseHandler(_handler).numberOfCalls("UBBasicHandler.addQuoteToken")); + console.log("BBasicHandler.removeQuoteToken ", IBaseHandler(_handler).numberOfCalls("BBasicHandler.removeQuoteToken")); + console.log("UBBasicHandler.removeQuoteToken ", IBaseHandler(_handler).numberOfCalls("UBBasicHandler.removeQuoteToken")); + console.log("BBasicHandler.addCollateral ", IBaseHandler(_handler).numberOfCalls("BBasicHandler.addCollateral")); + console.log("UBBasicHandler.addCollateral ", IBaseHandler(_handler).numberOfCalls("UBBasicHandler.addCollateral")); + console.log("BBasicHandler.removeCollateral ", IBaseHandler(_handler).numberOfCalls("BBasicHandler.removeCollateral")); + console.log("UBBasicHandler.removeCollateral ", IBaseHandler(_handler).numberOfCalls("UBBasicHandler.removeCollateral")); + console.log("--Borrower--------"); + console.log("BBasicHandler.drawDebt ", IBaseHandler(_handler).numberOfCalls("BBasicHandler.drawDebt")); + console.log("UBBasicHandler.drawDebt ", IBaseHandler(_handler).numberOfCalls("UBBasicHandler.drawDebt")); + console.log("BBasicHandler.repayDebt ", IBaseHandler(_handler).numberOfCalls("BBasicHandler.repayDebt")); + console.log("UBBasicHandler.repayDebt ", IBaseHandler(_handler).numberOfCalls("UBBasicHandler.repayDebt")); + console.log("------------------"); + console.log( + "Sum", + IBaseHandler(_handler).numberOfCalls("BBasicHandler.addQuoteToken") + + IBaseHandler(_handler).numberOfCalls("BBasicHandler.removeQuoteToken") + + IBaseHandler(_handler).numberOfCalls("BBasicHandler.addCollateral") + + IBaseHandler(_handler).numberOfCalls("BBasicHandler.removeCollateral") + + IBaseHandler(_handler).numberOfCalls("BBasicHandler.drawDebt") + + IBaseHandler(_handler).numberOfCalls("BBasicHandler.repayDebt") + ); + } + +} \ No newline at end of file diff --git a/tests/forge/ERC20Pool/invariants/InvariantTest.sol b/tests/forge/ERC20Pool/invariants/InvariantTest.sol new file mode 100644 index 000000000..a1e7ab0e6 --- /dev/null +++ b/tests/forge/ERC20Pool/invariants/InvariantTest.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.14; + +contract InvariantTest { + + struct FuzzSelector { + address addr; + bytes4[] selectors; + } + + address[] private _excludedContracts; + address[] private _excludedSenders; + address[] private _targetedContracts; + address[] private _targetedSenders; + + FuzzSelector[] internal _targetedSelectors; + + function excludeContract(address newExcludedContract_) internal { + _excludedContracts.push(newExcludedContract_); + } + + function excludeContracts() public view returns (address[] memory excludedContracts_) { + require(_excludedContracts.length != uint256(0), "NO_EXCLUDED_CONTRACTS"); + excludedContracts_ = _excludedContracts; + } + + function excludeSender(address newExcludedSender_) internal { + _excludedSenders.push(newExcludedSender_); + } + + function excludeSenders() public view returns (address[] memory excludedSenders_) { + require(_excludedSenders.length != uint256(0), "NO_EXCLUDED_SENDERS"); + excludedSenders_ = _excludedSenders; + } + + function targetContract(address newTargetedContract_) internal { + _targetedContracts.push(newTargetedContract_); + } + + function targetContracts() public view returns (address[] memory targetedContracts_) { + require(_targetedContracts.length != uint256(0), "NO_TARGETED_CONTRACTS"); + targetedContracts_ = _targetedContracts; + } + + function targetSelector(FuzzSelector memory newTargetedSelector_) internal { + _targetedSelectors.push(newTargetedSelector_); + } + + function targetSelectors() public view returns (FuzzSelector[] memory targetedSelectors_) { + require(targetedSelectors_.length != uint256(0), "NO_TARGETED_SELECTORS"); + targetedSelectors_ = _targetedSelectors; + } + + function targetSender(address newTargetedSender_) internal { + _targetedSenders.push(newTargetedSender_); + } + + function targetSenders() public view returns (address[] memory targetedSenders_) { + require(_targetedSenders.length != uint256(0), "NO_TARGETED_SENDERS"); + targetedSenders_ = _targetedSenders; + } + + /************************/ + /*** Helper Functions ***/ + /************************/ + + function getDiff(uint256 x, uint256 y) internal pure returns (uint256 diff) { + diff = x > y ? x - y : y - x; + } + + function requireWithinDiff(uint256 x, uint256 y, uint256 expectedDiff, string memory err) internal pure { + require(getDiff(x, y) <= expectedDiff, err); + } + +} \ No newline at end of file diff --git a/tests/forge/ERC20Pool/invariants/LiquidationInvariant.t.sol b/tests/forge/ERC20Pool/invariants/LiquidationInvariant.t.sol new file mode 100644 index 000000000..b455e9d8b --- /dev/null +++ b/tests/forge/ERC20Pool/invariants/LiquidationInvariant.t.sol @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.14; + +import '@std/Test.sol'; +import "@std/console.sol"; + +import { TestBase } from './TestBase.sol'; + +import { LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX, BORROWER_MIN_BUCKET_INDEX } from './handlers/BasicPoolHandler.sol'; + +import { LiquidationPoolHandler } from './handlers/LiquidationPoolHandler.sol'; +import { BasicInvariants } from './BasicInvariants.t.sol'; +import { IBaseHandler } from './handlers/IBaseHandler.sol'; + +contract LiquidationInvariant is BasicInvariants { + + /**************************************************************************************************************************************/ + /*** Invariant Tests ***/ + /*************************************************************************************************************************************** + * Auction + * A1: totalDebtInAuction = sum of all debt of all borrowers kicked + * A2: totalBondEscrowed = sum of all kicker's bond = total Bond in Auction + * A3: number of borrowers with debt = number of loans + number of auctioned borrowers + * A4: number of auctions = total borrowers kicked + * A5: for each auction, kicker locked bond is more than equal to auction bond + ****************************************************************************************************************************************/ + + LiquidationPoolHandler internal _liquidationPoolHandler; + + function setUp() public override virtual{ + + super.setUp(); + + excludeContract(address(_basicPoolHandler)); + + _liquidationPoolHandler = new LiquidationPoolHandler(address(_pool), address(_quote), address(_collateral), address(_poolInfo), NUM_ACTORS); + _handler = address(_liquidationPoolHandler); + } + + // checks sum of all borrower's t0debt is equals to total pool t0debtInAuction + function invariant_debtInAuction_A1() public view { + uint256 actorCount = IBaseHandler(_handler).getActorsCount(); + uint256 totalT0debtInAuction; + for(uint256 i = 0; i < actorCount; i++) { + address borrower = IBaseHandler(_handler).actors(i); + (, , , uint256 kickTime, , , , , ) = _pool.auctionInfo(borrower); + if(kickTime != 0) { + (uint256 t0debt, , ) = _pool.borrowerInfo(borrower); + totalT0debtInAuction += t0debt; + } + } + require(_pool.totalT0DebtInAuction() == totalT0debtInAuction, "Incorrect debt in auction"); + } + + // checks sum of all kicker bond is equal to total pool bond + function invariant_bond_A2() public view { + uint256 actorCount = IBaseHandler(_handler).getActorsCount(); + uint256 totalKickerBond; + for(uint256 i = 0; i < actorCount; i++) { + address kicker = IBaseHandler(_handler).actors(i); + (, uint256 bond) = _pool.kickerInfo(kicker); + totalKickerBond += bond; + } + + uint256 totalBondInAuction; + + for(uint256 i = 0; i < actorCount; i++) { + address borrower = IBaseHandler(_handler).actors(i); + (, , uint256 bondSize, , , , , , ) = _pool.auctionInfo(borrower); + totalBondInAuction += bondSize; + } + + require(totalBondInAuction == totalKickerBond, "Incorrect bond"); + + (uint256 totalPoolBond, , ) = _pool.reservesInfo(); + + require(totalPoolBond == totalKickerBond, "Incorrect bond"); + } + + // checks total borrowers with debt is equals to sum of borrowers unkicked and borrowers kicked + // checks total auctions is equals to total borrowers kicked + function invariant_auctions_A3_A4() public view { + uint256 actorCount = IBaseHandler(_handler).getActorsCount(); + uint256 totalBorrowersWithDebt; + for(uint256 i = 0; i < actorCount; i++) { + address borrower = IBaseHandler(_handler).actors(i); + (uint256 t0Debt, , ) = _pool.borrowerInfo(borrower); + if(t0Debt > 0) { + totalBorrowersWithDebt += 1; + } + } + ( , , uint256 loansCount) = _pool.loansInfo(); + uint256 totalAuction = _pool.totalAuctionsInPool(); + require(totalBorrowersWithDebt == loansCount + totalAuction, "incorrect no of borrowers in LoanState"); + + uint256 borrowersKicked; + for(uint256 i = 0; i < actorCount; i++) { + address borrower = IBaseHandler(_handler).actors(i); + (, , , uint256 kickTime, , , , , ) = _pool.auctionInfo(borrower); + if(kickTime != 0) { + borrowersKicked += 1; + } + } + require(borrowersKicked == totalAuction, "Incorrect borrowers in auction"); + } + + function invariant_borrowers_A5() public view { + uint256 actorCount = IBaseHandler(_handler).getActorsCount(); + for(uint256 i = 0; i < actorCount; i++) { + address borrower = IBaseHandler(_handler).actors(i); + (address kicker, , uint256 bondSize, , , , , , ) = _pool.auctionInfo(borrower); + (, uint256 lockedAmount) = _pool.kickerInfo(kicker); + require(lockedAmount >= bondSize, "Incorrect bond locked"); + } + } + + function invariant_call_summary() external view virtual override{ + console.log("\nCall Summary\n"); + console.log("--Lender----------"); + console.log("BLiquidationHandler.addQuoteToken ", IBaseHandler(_handler).numberOfCalls("BBasicHandler.addQuoteToken")); + console.log("UBLiquidationHandler.addQuoteToken ", IBaseHandler(_handler).numberOfCalls("UBBasicHandler.addQuoteToken")); + console.log("BLiquidationHandler.removeQuoteToken ", IBaseHandler(_handler).numberOfCalls("BBasicHandler.removeQuoteToken")); + console.log("UBLiquidationHandler.removeQuoteToken ", IBaseHandler(_handler).numberOfCalls("UBBasicHandler.removeQuoteToken")); + console.log("BLiquidationHandler.addCollateral ", IBaseHandler(_handler).numberOfCalls("BBasicHandler.addCollateral")); + console.log("UBLiquidationHandler.addCollateral ", IBaseHandler(_handler).numberOfCalls("UBBasicHandler.addCollateral")); + console.log("BLiquidationHandler.removeCollateral ", IBaseHandler(_handler).numberOfCalls("BBasicHandler.removeCollateral")); + console.log("UBLiquidationHandler.removeCollateral ", IBaseHandler(_handler).numberOfCalls("UBBasicHandler.removeCollateral")); + console.log("--Borrower--------"); + console.log("BLiquidationHandler.drawDebt ", IBaseHandler(_handler).numberOfCalls("BBasicHandler.drawDebt")); + console.log("UBLiquidationHandler.drawDebt ", IBaseHandler(_handler).numberOfCalls("UBBasicHandler.drawDebt")); + console.log("BLiquidationHandler.repayDebt ", IBaseHandler(_handler).numberOfCalls("BBasicHandler.repayDebt")); + console.log("UBLiquidationHandler.repayDebt ", IBaseHandler(_handler).numberOfCalls("UBBasicHandler.repayDebt")); + console.log("BLiquidationHandler.kickAuction ", IBaseHandler(_handler).numberOfCalls("BLiquidationHandler.kickAuction")); + console.log("UBLiquidationHandler.kickAuction ", IBaseHandler(_handler).numberOfCalls("UBLiquidationHandler.kickAuction")); + console.log("BLiquidationHandler.takeAuction ", IBaseHandler(_handler).numberOfCalls("BLiquidationHandler.takeAuction")); + console.log("UBLiquidationHandler.takeAuction ", IBaseHandler(_handler).numberOfCalls("UBLiquidationHandler.takeAuction")); + console.log("------------------"); + console.log( + "Sum", + IBaseHandler(_handler).numberOfCalls("BBasicHandler.addQuoteToken") + + IBaseHandler(_handler).numberOfCalls("BBasicHandler.removeQuoteToken") + + IBaseHandler(_handler).numberOfCalls("BBasicHandler.addCollateral") + + IBaseHandler(_handler).numberOfCalls("BBasicHandler.removeCollateral") + + IBaseHandler(_handler).numberOfCalls("BBasicHandler.drawDebt") + + IBaseHandler(_handler).numberOfCalls("BBasicHandler.repayDebt") + + IBaseHandler(_handler).numberOfCalls("BLiquidationHandler.kickAuction") + + IBaseHandler(_handler).numberOfCalls("BLiquidationHandler.takeAuction") + ); + } + +} \ No newline at end of file diff --git a/tests/forge/ERC20Pool/invariants/ReserveInvariants.t.sol b/tests/forge/ERC20Pool/invariants/ReserveInvariants.t.sol new file mode 100644 index 000000000..0b9095fc8 --- /dev/null +++ b/tests/forge/ERC20Pool/invariants/ReserveInvariants.t.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.14; + +import '@std/Test.sol'; +import "@std/console.sol"; + +import { TestBase } from './TestBase.sol'; + +import { LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX, BORROWER_MIN_BUCKET_INDEX } from './handlers/BasicPoolHandler.sol'; + +import { ReservePoolHandler } from './handlers/ReservePoolHandler.sol'; +import { LiquidationInvariant } from './LiquidationInvariant.t.sol'; +import { IBaseHandler } from './handlers/IBaseHandler.sol'; + +contract ReserveInvariants is LiquidationInvariant { + + ReservePoolHandler internal _reservePoolHandler; + uint256 previousReserves; + + function setUp() public override virtual { + + super.setUp(); + + excludeContract(address(_liquidationPoolHandler)); + + _reservePoolHandler = new ReservePoolHandler(address(_pool), address(_quote), address(_collateral), address(_poolInfo), NUM_ACTORS); + _handler = address(_reservePoolHandler); + + (previousReserves, , , , ) = _poolInfo.poolReservesInfo(address(_pool)); + } + + // FIXME + function _invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9() public { + + (uint256 currentReserves, , , , ) = _poolInfo.poolReservesInfo(address(_pool)); + console.log("Current Reserves -->", currentReserves); + console.log("Previous Reserves -->", previousReserves); + if(!IBaseHandler(_handler).shouldReserveChange()) { + require(currentReserves == previousReserves, "Incorrect Reserves change"); + } + + uint256 firstTakeIncreaseInReserve = IBaseHandler(_handler).firstTakeIncreaseInReserve(); + + console.log("firstTakeIncreaseInReserve -->", firstTakeIncreaseInReserve); + if(IBaseHandler(_handler).firstTake()) { + requireWithinDiff(currentReserves, previousReserves + firstTakeIncreaseInReserve, 1e2, "Incorrect Reserves change with first take"); + } + + uint256 loanKickIncreaseInReserve = IBaseHandler(_handler).loanKickIncreaseInReserve(); + + console.log("loanKickIncreaseInReserve -->", loanKickIncreaseInReserve); + if(loanKickIncreaseInReserve != 0) { + requireWithinDiff(currentReserves, previousReserves + loanKickIncreaseInReserve, 1e2, "Incorrect Reserves change with kick"); + } + previousReserves = currentReserves; + } + +} \ No newline at end of file diff --git a/tests/forge/ERC20Pool/invariants/TestBase.sol b/tests/forge/ERC20Pool/invariants/TestBase.sol new file mode 100644 index 000000000..1e626e676 --- /dev/null +++ b/tests/forge/ERC20Pool/invariants/TestBase.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.14; + +import '@std/Test.sol'; +import "forge-std/console.sol"; + +import { ERC20Pool } from 'src/ERC20Pool.sol'; +import { ERC20PoolFactory } from 'src/ERC20PoolFactory.sol'; +import { Token } from '../../utils/Tokens.sol'; +import { PoolInfoUtils } from 'src/PoolInfoUtils.sol'; +import { InvariantTest } from './InvariantTest.sol'; + +contract TestBase is InvariantTest, Test { + + // Mainnet ajna address + address internal _ajna = 0x9a96ec9B57Fb64FbC60B423d1f4da7691Bd35079; + + Token internal _quote; + Token internal _collateral; + + ERC20Pool internal _pool; + ERC20Pool internal _impl; + PoolInfoUtils internal _poolInfo; + ERC20PoolFactory internal _poolFactory; + + function setUp() public virtual { + // Tokens + _quote = new Token("Quote", "Q"); + _collateral = new Token("Collateral", "C"); + + // Pool + _poolFactory = new ERC20PoolFactory(_ajna); + _pool = ERC20Pool(_poolFactory.deployPool(address(_collateral), address(_quote), 0.05 * 10**18)); + _poolInfo = new PoolInfoUtils(); + _impl = _poolFactory.implementation(); + } +} \ No newline at end of file diff --git a/tests/forge/ERC20Pool/invariants/handlers/BaseHandler.sol b/tests/forge/ERC20Pool/invariants/handlers/BaseHandler.sol new file mode 100644 index 000000000..863ece656 --- /dev/null +++ b/tests/forge/ERC20Pool/invariants/handlers/BaseHandler.sol @@ -0,0 +1,153 @@ + +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.14; + +import { Strings } from '@openzeppelin/contracts/utils/Strings.sol'; +import '@std/Test.sol'; +import '@std/Vm.sol'; +import "forge-std/console.sol"; + +import { ERC20Pool } from 'src/ERC20Pool.sol'; +import { ERC20PoolFactory } from 'src/ERC20PoolFactory.sol'; +import { Token } from '../../../utils/Tokens.sol'; +import { PoolInfoUtils } from 'src/PoolInfoUtils.sol'; +import { InvariantTest } from '../InvariantTest.sol'; + + +uint256 constant LENDER_MIN_BUCKET_INDEX = 2570; +uint256 constant LENDER_MAX_BUCKET_INDEX = 2590; + +uint256 constant BORROWER_MIN_BUCKET_INDEX = 2600; +uint256 constant BORROWER_MAX_BUCKET_INDEX = 2620; + +contract BaseHandler is InvariantTest, Test { + + // Tokens + Token internal _quote; + Token internal _collateral; + + // Pool + ERC20Pool internal _pool; + PoolInfoUtils internal _poolInfo; + + // Modifiers + address internal _actor; + uint256 internal _lenderBucketIndex; + uint256 internal _limitIndex; + address[] public actors; + + // Logging + mapping(bytes32 => uint256) public numberOfCalls; + + // Lender tracking + mapping(address => uint256[]) public touchedBuckets; + + // bucket exchange rate invariant check + bool public shouldExchangeRateChange; + + bool public shouldReserveChange; + + // if take is called on auction first time + bool public firstTake; + + // mapping borrower address to first take on auction + mapping(address => bool) internal isFirstTakeOnAuction; + + // amount of reserve increase after first take + uint256 public firstTakeIncreaseInReserve; + + // amount of reserve increase after kicking a loan + uint256 public loanKickIncreaseInReserve; + + constructor(address pool, address quote, address collateral, address poolInfo, uint256 numOfActors) { + // Tokens + _quote = Token(quote); + _collateral = Token(collateral); + + // Pool + _pool = ERC20Pool(pool); + _poolInfo = PoolInfoUtils(poolInfo); + + // Actors + actors = _buildActors(numOfActors); + } + + /**************************************************************************************************************************************/ + /*** Helper Functions ***/ + /**************************************************************************************************************************************/ + + modifier useRandomActor(uint256 actorIndex) { + // pre condition + firstTakeIncreaseInReserve = 0; + loanKickIncreaseInReserve = 0; + + vm.stopPrank(); + + address actor = actors[constrictToRange(actorIndex, 0, actors.length - 1)]; + _actor = actor; + vm.startPrank(actor); + _; + vm.stopPrank(); + } + + modifier useRandomLenderBucket(uint256 bucketIndex) { + uint256[] storage lenderBucketIndexes = touchedBuckets[_actor]; + if (lenderBucketIndexes.length < 3) { + // if actor has touched less than three buckets, add a new bucket + _lenderBucketIndex = constrictToRange(bucketIndex, LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX); + lenderBucketIndexes.push(_lenderBucketIndex); + } else { + // if actor has touched more than three buckets, reuse one of the touched buckets + _lenderBucketIndex = lenderBucketIndexes[constrictToRange(bucketIndex, 0, lenderBucketIndexes.length - 1)]; + } + _; + } + + function _buildActors(uint256 noOfActors_) internal returns(address[] memory) { + address[] memory actorsAddress = new address[](noOfActors_); + for(uint i = 0; i < noOfActors_; i++) { + address actor = makeAddr(string(abi.encodePacked("Actor", Strings.toString(i)))); + actorsAddress[i] = actor; + + vm.startPrank(actor); + + _quote.mint(actor, 1e45); + _quote.approve(address(_pool), 1e45); + + _collateral.mint(actor, 1e45); + _collateral.approve(address(_pool), 1e45); + + vm.stopPrank(); + } + return actorsAddress; + } + + function getActorsCount() external view returns(uint256) { + return actors.length; + } + + function constrictToRange( + uint256 x, + uint256 min, + uint256 max + ) pure public returns (uint256 result) { + require(max >= min, "MAX_LESS_THAN_MIN"); + + uint256 size = max - min; + + if (size == 0) return min; // Using max would be equivalent as well. + if (max != type(uint256).max) size++; // Make the max inclusive. + + // Ensure max is inclusive in cases where x != 0 and max is at uint max. + if (max == type(uint256).max && x != 0) x--; // Accounted for later. + + if (x < min) x += size * (((min - x) / size) + 1); + + result = min + ((x - min) % size); + + // Account for decrementing x to make max inclusive. + if (max == type(uint256).max && x != 0) result++; + } + +} \ No newline at end of file diff --git a/tests/forge/ERC20Pool/invariants/handlers/BasicPoolHandler.sol b/tests/forge/ERC20Pool/invariants/handlers/BasicPoolHandler.sol new file mode 100644 index 000000000..84edad7fb --- /dev/null +++ b/tests/forge/ERC20Pool/invariants/handlers/BasicPoolHandler.sol @@ -0,0 +1,359 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.14; + +import { Strings } from '@openzeppelin/contracts/utils/Strings.sol'; +import "forge-std/console.sol"; +import '@std/Test.sol'; +import '@std/Vm.sol'; + +import { ERC20Pool } from 'src/ERC20Pool.sol'; +import { ERC20PoolFactory } from 'src/ERC20PoolFactory.sol'; +import { Token } from '../../../utils/Tokens.sol'; +import { PoolInfoUtils, _collateralization } from 'src/PoolInfoUtils.sol'; + +import { LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX, BORROWER_MIN_BUCKET_INDEX, BaseHandler } from './BaseHandler.sol'; + +/** + * @dev this contract manages multiple lenders + * @dev methods in this contract are called in random order + * @dev randomly selects a lender contract to make a txn + */ +abstract contract UnboundedBasicPoolHandler is BaseHandler { + + /**************************************************************************************************************************************/ + /*** Lender Functions ***/ + /**************************************************************************************************************************************/ + + function addQuoteToken(uint256 amount, uint256 bucketIndex) internal { + numberOfCalls['UBBasicHandler.addQuoteToken']++; + + shouldExchangeRateChange = false; + shouldReserveChange = false; + + // Pre condition + (uint256 lpBalanceBefore, ) = _pool.lenderInfo(bucketIndex, _actor); + + _pool.addQuoteToken(amount, bucketIndex, block.timestamp + 1 minutes); + + // Post condition + (uint256 lpBalanceAfter, ) = _pool.lenderInfo(bucketIndex, _actor); + require(lpBalanceAfter > lpBalanceBefore, "LP balance should increase"); + } + + function removeQuoteToken(uint256 amount, uint256 bucketIndex) internal { + numberOfCalls['UBBasicHandler.removeQuoteToken']++; + + // Pre condition + (uint256 lpBalanceBefore, ) = _pool.lenderInfo(bucketIndex, _actor); + + if (lpBalanceBefore == 0) { + amount = constrictToRange(amount, 1, 1e36); + addQuoteToken(amount, bucketIndex); + } + + (lpBalanceBefore, ) = _pool.lenderInfo(bucketIndex, _actor); + + try _pool.removeQuoteToken(amount, bucketIndex) { + // Post condition + (uint256 lpBalanceAfter, ) = _pool.lenderInfo(bucketIndex, _actor); + require(lpBalanceAfter < lpBalanceBefore, "LP balance should decrease"); + shouldExchangeRateChange = false; + shouldReserveChange = false; + } + catch (bytes memory _err){ + bytes32 err = keccak256(_err); + require( + err == keccak256(abi.encodeWithSignature("LUPBelowHTP()")) || + err == keccak256(abi.encodeWithSignature("InsufficientLiquidity()")) || + err == keccak256(abi.encodeWithSignature("RemoveDepositLockedByAuctionDebt()")) || + err == keccak256(abi.encodeWithSignature("NoClaim()"))); + } + } + + function moveQuoteToken(uint256 amount, uint256 fromIndex, uint256 toIndex) internal { + if(fromIndex == toIndex) return; + + (uint256 lpBalance, ) = _pool.lenderInfo(fromIndex, _actor); + + if (lpBalance == 0) { + addQuoteToken(amount, fromIndex); + } + + try _pool.moveQuoteToken(amount, fromIndex, toIndex, block.timestamp + 1 minutes) { + shouldExchangeRateChange = false; + shouldReserveChange = false; + } + catch (bytes memory _err){ + bytes32 err = keccak256(_err); + require( + err == keccak256(abi.encodeWithSignature("LUPBelowHTP()")) || + err == keccak256(abi.encodeWithSignature("InsufficientLiquidity()")) || + err == keccak256(abi.encodeWithSignature("MoveToSamePrice()")) || + err == keccak256(abi.encodeWithSignature("DustAmountNotExceeded()")) || + err == keccak256(abi.encodeWithSignature("InvalidIndex()")) || + err == keccak256(abi.encodeWithSignature("BucketBankruptcyBlock()")) + ); + } + } + + function addCollateral(uint256 amount, uint256 bucketIndex) internal { + numberOfCalls['UBBasicHandler.addCollateral']++; + + shouldExchangeRateChange = false; + shouldReserveChange = false; + + // Pre condition + (uint256 lpBalanceBefore, ) = _pool.lenderInfo(bucketIndex, _actor); + + _pool.addCollateral(amount, bucketIndex, block.timestamp + 1 minutes); + + // Post condition + (uint256 lpBalanceAfter, ) = _pool.lenderInfo(bucketIndex, _actor); + require(lpBalanceAfter > lpBalanceBefore, "LP balance should increase"); + } + + function removeCollateral(uint256 amount, uint256 bucketIndex) internal { + numberOfCalls['UBBasicHandler.removeCollateral']++; + + shouldExchangeRateChange = false; + shouldReserveChange = false; + + // Pre condition + (uint256 lpBalanceBefore, ) = _pool.lenderInfo(bucketIndex, _actor); + + if(lpBalanceBefore == 0) { + addCollateral(amount, bucketIndex); + } + + (lpBalanceBefore, ) = _pool.lenderInfo(bucketIndex, _actor); + + _pool.removeCollateral(amount, bucketIndex); + + // Post condition + (uint256 lpBalanceAfter, ) = _pool.lenderInfo(bucketIndex, _actor); + require(lpBalanceAfter < lpBalanceBefore, "LP balance should decrease"); + } + + /**************************/ + /*** Borrower Functions ***/ + /**************************/ + + function pledgeCollateral(uint256 amount) internal { + numberOfCalls['UBBasicHandler.pledgeCollateral']++; + + shouldExchangeRateChange = false; + shouldReserveChange = false; + + _pool.drawDebt(_actor, 0, 0, amount); + } + + function pullCollateral(uint256 amount) internal { + numberOfCalls['UBBasicHandler.pullCollateral']++; + + try _pool.repayDebt(_actor, 0, amount, _actor, 7388) { + shouldExchangeRateChange = false; + shouldReserveChange = false; + } catch (bytes memory _err){ + bytes32 err = keccak256(_err); + require( + err == keccak256(abi.encodeWithSignature("InsufficientCollateral()")) || + err == keccak256(abi.encodeWithSignature("AuctionActive()")) + ); + } + } + + function drawDebt(uint256 amount) internal { + numberOfCalls['UBBasicHandler.drawDebt']++; + + // Pre Condition + // 1. borrower's debt should exceed minDebt + // 2. pool needs sufficent quote token to draw debt + // 3. drawDebt should not make borrower under collateralized + + // 1. borrower's debt should exceed minDebt + (uint256 debt, uint256 collateral, ) = _poolInfo.borrowerInfo(address(_pool), _actor); + (uint256 minDebt, , , ) = _poolInfo.poolUtilizationInfo(address(_pool)); + if (amount < minDebt) amount = minDebt + 1; + + + // TODO: Need to constrain amount so LUP > HTP + + + // 2. pool needs sufficent quote token to draw debt + uint256 poolQuoteBalance = _quote.balanceOf(address(_pool)); + + if (amount > poolQuoteBalance) { + addQuoteToken(amount * 2, LENDER_MAX_BUCKET_INDEX); + } + + // 3. drawing of addition debt will make them under collateralized + uint256 lup = _poolInfo.lup(address(_pool)); + (debt, collateral, ) = _poolInfo.borrowerInfo(address(_pool), _actor); + + if (_collateralization(debt, collateral, lup) < 1) { + repayDebt(debt); + (debt, collateral, ) = _poolInfo.borrowerInfo(address(_pool), _actor); + require(debt == 0, "borrower has debt"); + } + + (uint256 poolDebt, , ) = _pool.debtInfo(); + + // find bucket to borrow quote token + uint256 bucket = _pool.depositIndex(amount + poolDebt) - 1; + + uint256 price = _poolInfo.indexToPrice(bucket); + + uint256 collateralToPledge = ((amount * 1e18 + price / 2) / price) * 101 / 100; + + try _pool.drawDebt(_actor, amount, 7388, collateralToPledge) { + shouldExchangeRateChange = true; + shouldReserveChange = true; + } + catch (bytes memory _err){ + bytes32 err = keccak256(_err); + require( + err == keccak256(abi.encodeWithSignature("BorrowerUnderCollateralized()")) || + err == keccak256(abi.encodeWithSignature("AuctionActive()")) + ); + } + } + + function repayDebt(uint256 amountToRepay) internal { + numberOfCalls['UBBasicHandler.repayDebt']++; + + // Pre condition + (uint256 debt, , ) = PoolInfoUtils(_poolInfo).borrowerInfo(address(_pool), _actor); + if (debt == 0) { + drawDebt(amountToRepay); + } + + try _pool.repayDebt(_actor, amountToRepay, 0, _actor, 7388) { + shouldExchangeRateChange = true; + shouldReserveChange = true; + } + catch(bytes memory _err) { + bytes32 err = keccak256(_err); + require( + err == keccak256(abi.encodeWithSignature("NoDebt()")) || + err == keccak256(abi.encodeWithSignature("AmountLTMinDebt()")) + ); + } + } + +} + + +/** + * @dev this contract manages multiple lenders + * @dev methods in this contract are called in random order + * @dev randomly selects a lender contract to make a txn + */ +contract BasicPoolHandler is UnboundedBasicPoolHandler { + + constructor(address pool, address quote, address collateral, address poolInfo, uint256 numOfActors) BaseHandler(pool, quote, collateral, poolInfo, numOfActors) {} + + /**************************/ + /*** Lender Functions ***/ + /**************************/ + + function addQuoteToken(uint256 actorIndex, uint256 amount, uint256 bucketIndex) public useRandomActor(actorIndex) useRandomLenderBucket(bucketIndex) { + numberOfCalls['BBasicHandler.addQuoteToken']++; + + uint256 totalSupply = _quote.totalSupply(); + uint256 minDeposit = totalSupply == 0 ? 1 : _quote.balanceOf(address(_actor)) / totalSupply + 1; + amount = constrictToRange(amount, minDeposit, 1e36); + + // Action + super.addQuoteToken(amount, _lenderBucketIndex); + } + + function removeQuoteToken(uint256 actorIndex, uint256 amount, uint256 bucketIndex) public useRandomActor(actorIndex) useRandomLenderBucket(bucketIndex) { + numberOfCalls['BBasicHandler.removeQuoteToken']++; + + uint256 poolBalance = _quote.balanceOf(address(_pool)); + + if (poolBalance < amount) return; // (not enough quote token to withdraw / quote tokens are borrowed) + + // Action + super.removeQuoteToken(amount, _lenderBucketIndex); + } + + function moveQuoteToken(uint256 actorIndex, uint256 amount, uint256 fromBucketIndex, uint256 toBucketIndex) public useRandomActor(actorIndex) { + numberOfCalls['BBasicHandler.moveQuoteToken']++; + + fromBucketIndex = constrictToRange(fromBucketIndex, LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX); + + toBucketIndex = constrictToRange(toBucketIndex, LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX); + + amount = constrictToRange(amount, 1, 1e36); + + super.moveQuoteToken(amount, fromBucketIndex, toBucketIndex); + } + + function addCollateral(uint256 actorIndex, uint256 amount, uint256 bucketIndex) public useRandomActor(actorIndex) useRandomLenderBucket(bucketIndex) { + numberOfCalls['BBasicHandler.addCollateral']++; + + amount = constrictToRange(amount, 1, 1e36); + + // Action + super.addCollateral(amount, _lenderBucketIndex); + } + + function removeCollateral(uint256 actorIndex, uint256 amount, uint256 bucketIndex) public useRandomActor(actorIndex) useRandomLenderBucket(bucketIndex) { + numberOfCalls['BBasicHandler.removeCollateral']++; + + (uint256 lpBalance, ) = _pool.lenderInfo(_lenderBucketIndex, _actor); + ( , uint256 bucketCollateral, , , ) = _pool.bucketInfo(_lenderBucketIndex); + + if (lpBalance == 0 || bucketCollateral == 0) return; // no value in bucket + + amount = constrictToRange(amount, 1, 1e36); + + // Action + super.removeCollateral(amount, _lenderBucketIndex); + } + + + /**************************/ + /*** Borrower Functions ***/ + /**************************/ + + function pledgeCollateral(uint256 actorIndex, uint256 amountToPledge) public useRandomActor(actorIndex) { + numberOfCalls['BBasicHandler.pledgeCollateral']++; + + amountToPledge = constrictToRange(amountToPledge, 1, 1e36); + + // Action + super.pledgeCollateral(amountToPledge); + } + + function pullCollateral(uint256 actorIndex, uint256 amountToPull) public useRandomActor(actorIndex) { + numberOfCalls['BBasicHandler.pullCollateral']++; + + amountToPull = constrictToRange(amountToPull, 1, 1e36); + + // Action + super.pullCollateral(amountToPull); + } + + function drawDebt(uint256 actorIndex, uint256 amountToBorrow) public useRandomActor(actorIndex) { + numberOfCalls['BBasicHandler.drawDebt']++; + + amountToBorrow = constrictToRange(amountToBorrow, 1, 1e36); + + // Action + super.drawDebt(amountToBorrow); + + // skip time to make borrower undercollateralized + vm.warp(block.timestamp + 200 days); + } + + function repayDebt(uint256 actorIndex, uint256 amountToRepay) public useRandomActor(actorIndex) { + numberOfCalls['BBasicHandler.repayDebt']++; + + amountToRepay = constrictToRange(amountToRepay, 1, 1e36); + + // Action + super.repayDebt(amountToRepay); + } +} diff --git a/tests/forge/ERC20Pool/invariants/handlers/IBaseHandler.sol b/tests/forge/ERC20Pool/invariants/handlers/IBaseHandler.sol new file mode 100644 index 000000000..092c21f7e --- /dev/null +++ b/tests/forge/ERC20Pool/invariants/handlers/IBaseHandler.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.14; + +interface IBaseHandler { + + function getActorsCount() external view returns(uint256); + + function actors(uint256) external view returns(address); + + function numberOfCalls(bytes32) external view returns(uint256); + + function shouldExchangeRateChange() external view returns(bool); + + function shouldReserveChange() external view returns(bool); + + function firstTake() external view returns(bool); + + function firstTakeIncreaseInReserve() external view returns(uint256); + + function loanKickIncreaseInReserve() external view returns(uint256); +} \ No newline at end of file diff --git a/tests/forge/ERC20Pool/invariants/handlers/LiquidationPoolHandler.sol b/tests/forge/ERC20Pool/invariants/handlers/LiquidationPoolHandler.sol new file mode 100644 index 000000000..3c3bdc319 --- /dev/null +++ b/tests/forge/ERC20Pool/invariants/handlers/LiquidationPoolHandler.sol @@ -0,0 +1,148 @@ + +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.14; + +import '@std/Vm.sol'; + +import { BasicPoolHandler } from './BasicPoolHandler.sol'; +import { LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX, BaseHandler } from './BaseHandler.sol'; +import { Maths } from 'src/libraries/internal/Maths.sol'; + +abstract contract UnBoundedLiquidationPoolHandler is BaseHandler { + function kickAuction(address borrower) internal { + numberOfCalls['UBLiquidationHandler.kickAuction']++; + + (uint256 borrowerDebt, , ) = _poolInfo.borrowerInfo(address(_pool), borrower); + + try _pool.kick(borrower) { + shouldExchangeRateChange = true; + shouldReserveChange = true; + loanKickIncreaseInReserve = Maths.wmul(borrowerDebt, 0.25 * 1e18); + } + catch { + } + } + + function takeAuction(address borrower, uint256 amount, address taker) internal { + numberOfCalls['UBLiquidationHandler.takeAuction']++; + + (uint256 borrowerDebt, , ) = _poolInfo.borrowerInfo(address(_pool), borrower); + + try _pool.take(borrower, amount, taker, bytes("")) { + shouldExchangeRateChange = true; + shouldReserveChange = true; + + if(!isFirstTakeOnAuction[borrower]) { + firstTakeIncreaseInReserve = Maths.wmul(borrowerDebt, 0.07 * 1e18); + firstTake = true; + isFirstTakeOnAuction[borrower] = true; + } + else { + isFirstTakeOnAuction[borrower] = false; + firstTake = false; + } + } + catch { + } + } + + function bucketTake(address borrower, bool depositTake, uint256 bucketIndex) internal { + numberOfCalls['UBLiquidationHandler.bucketTake']++; + + (uint256 borrowerDebt, , ) = _poolInfo.borrowerInfo(address(_pool), borrower); + + try _pool.bucketTake(borrower, depositTake, bucketIndex) { + shouldExchangeRateChange = true; + shouldReserveChange = true; + + if(!firstTake) { + firstTakeIncreaseInReserve = Maths.wmul(borrowerDebt, 0.07 * 1e18); + firstTake = true; + } + else { + firstTake = false; + } + } + catch { + } + } +} + +contract LiquidationPoolHandler is UnBoundedLiquidationPoolHandler, BasicPoolHandler { + + constructor(address pool, address quote, address collateral, address poolInfo, uint256 numOfActors) BasicPoolHandler(pool, quote, collateral, poolInfo, numOfActors) {} + + function _kickAuction(uint256 borrowerIndex, uint256 amount, uint256 kickerIndex) internal useRandomActor(kickerIndex) { + numberOfCalls['BLiquidationHandler.kickAuction']++; + + shouldExchangeRateChange = true; + + borrowerIndex = constrictToRange(borrowerIndex, 0, actors.length - 1); + address borrower = actors[borrowerIndex]; + address kicker = _actor; + amount = constrictToRange(amount, 1, 1e36); + + ( , , , uint256 kickTime, , , , , ) = _pool.auctionInfo(borrower); + + if (kickTime == 0) { + (uint256 debt, , ) = _pool.borrowerInfo(borrower); + if (debt == 0) { + changePrank(borrower); + _actor = borrower; + super.drawDebt(amount); + } + changePrank(kicker); + _actor = kicker; + super.kickAuction(borrower); + } + + // skip some time for more interest + vm.warp(block.timestamp + 2 hours); + } + + function kickAuction(uint256 borrowerIndex, uint256 amount, uint256 kickerIndex) external { + _kickAuction(borrowerIndex, amount, kickerIndex); + } + + function takeAuction(uint256 borrowerIndex, uint256 amount, uint256 actorIndex) external useRandomActor(actorIndex){ + numberOfCalls['BLiquidationHandler.takeAuction']++; + + amount = constrictToRange(amount, 1, 1e36); + + shouldExchangeRateChange = true; + + borrowerIndex = constrictToRange(borrowerIndex, 0, actors.length - 1); + + address borrower = actors[borrowerIndex]; + address taker = _actor; + + ( , , , uint256 kickTime, , , , , ) = _pool.auctionInfo(borrower); + + if (kickTime == 0) { + _kickAuction(borrowerIndex, amount * 100, actorIndex); + } + changePrank(taker); + super.takeAuction(borrower, amount, taker); + } + + function bucketTake(uint256 borrowerIndex, uint256 bucketIndex, bool depositTake, uint256 takerIndex) external useRandomActor(takerIndex) { + numberOfCalls['BLiquidationHandler.bucketTake']++; + + shouldExchangeRateChange = true; + + borrowerIndex = constrictToRange(borrowerIndex, 0, actors.length - 1); + + bucketIndex = constrictToRange(bucketIndex, LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX); + + address borrower = actors[borrowerIndex]; + address taker = _actor; + + ( , , , uint256 kickTime, , , , , ) = _pool.auctionInfo(borrower); + + if (kickTime == 0) { + _kickAuction(borrowerIndex, 1e24, bucketIndex); + } + changePrank(taker); + super.bucketTake(borrower, depositTake, bucketIndex); + } +} \ No newline at end of file diff --git a/tests/forge/ERC20Pool/invariants/handlers/ReservePoolHandler.sol b/tests/forge/ERC20Pool/invariants/handlers/ReservePoolHandler.sol new file mode 100644 index 000000000..1e592a2e3 --- /dev/null +++ b/tests/forge/ERC20Pool/invariants/handlers/ReservePoolHandler.sol @@ -0,0 +1,48 @@ + +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.14; + +import '@std/Vm.sol'; + +import { LiquidationPoolHandler } from './LiquidationPoolHandler.sol'; +import { LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX, BaseHandler } from './BaseHandler.sol'; +import { Auctions } from 'src/libraries/external/Auctions.sol'; + +abstract contract UnBoundedReservePoolHandler is BaseHandler { + function startClaimableReserveAuction() internal { + (, uint256 claimableReserves, , , ) = _poolInfo.poolReservesInfo(address(_pool)); + if(claimableReserves == 0) return; + try _pool.startClaimableReserveAuction(){ + shouldReserveChange = true; + } catch { + } + } + + function takeReserves(uint256 amount) internal { + try _pool.takeReserves(amount){ + shouldReserveChange = true; + } catch { + } + } +} + +contract ReservePoolHandler is UnBoundedReservePoolHandler, LiquidationPoolHandler { + + constructor(address pool, address quote, address collateral, address poolInfo, uint256 numOfActors) LiquidationPoolHandler(pool, quote, collateral, poolInfo, numOfActors) {} + + function startClaimableReserveAuction(uint256 actorIndex) external useRandomActor(actorIndex) { + super.startClaimableReserveAuction(); + } + + function takeReserves(uint256 actorIndex, uint256 amount) external useRandomActor(actorIndex) { + (, , uint256 claimableReservesRemaining, , ) = _poolInfo.poolReservesInfo(address(_pool)); + + if(claimableReservesRemaining == 0) { + super.startClaimableReserveAuction(); + } + (, , claimableReservesRemaining, , ) = _poolInfo.poolReservesInfo(address(_pool)); + + amount = constrictToRange(amount, 0, claimableReservesRemaining); + super.takeReserves(amount); + } +} \ No newline at end of file diff --git a/tests/forge/ERC20Pool/regression/RegressionTestBasic.t.sol b/tests/forge/ERC20Pool/regression/RegressionTestBasic.t.sol new file mode 100644 index 000000000..57e0a9819 --- /dev/null +++ b/tests/forge/ERC20Pool/regression/RegressionTestBasic.t.sol @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.14; + +import { BasicInvariants } from "../invariants/BasicInvariants.t.sol"; + +import '@std/console.sol'; + +contract RegressionTestBasic is BasicInvariants { + + function setUp() public override { + super.setUp(); + } + + function test_regression_invariantUnderflow_1() external { + _basicPoolHandler.addQuoteToken(14227, 5211, 3600000000000000000000); + // check invariants hold true + invariant_Lps_B1(); + invariant_quoteTokenBalance_QT1(); + } + + function test_exchange_rate_bug_simulation() external { + // Action sequence + // 1. addQuoteToken(6879, 2570) + // 2. addCollateral(3642907759282013932739218713, 2570) + // 3. removeCollateral(296695924278944779257290397234298756, 2570) + + uint256 previousExchangeRate = 1e18; + _basicPoolHandler.addQuoteToken(999999999844396154169639088436193915956854451, 6879, 2809); + ( , uint256 quote, uint256 collateral, uint256 lps, , uint256 exchangeRate) = _poolInfo.bucketInfo(address(_pool), 2570); + console.log("After addQuoteToken(6879, 2570)"); + console.log("============"); + console.log("Quote Tokens -->", quote); + console.log("Collateral Tokens -->", collateral); + console.log("Lps -->", lps); + console.log("Exchange Rate-->", exchangeRate); + console.log("============"); + require(previousExchangeRate == exchangeRate, "Incorrect exchange rate"); + previousExchangeRate = exchangeRate; + _basicPoolHandler.addCollateral(2, 36429077592820139327392187131, 202214962129783771592); + ( , quote, collateral, lps, , exchangeRate) = _poolInfo.bucketInfo(address(_pool), 2570); + console.log("After addCollateral(3642907759282013932739218713, 2570)"); + console.log("============"); + console.log("Quote Tokens -->", quote); + console.log("Collateral Tokens -->", collateral); + console.log("Lps -->", lps); + console.log("Exchange Rate-->", exchangeRate); + console.log("============"); + require(previousExchangeRate == exchangeRate, "Incorrect exchange rate"); + previousExchangeRate = exchangeRate; + _basicPoolHandler.removeCollateral(1, 2296695924278944779257290397234298756, 10180568736759156593834642286260647915348262280903719122483474452532722106636); + ( , quote, collateral, lps, , exchangeRate) = _poolInfo.bucketInfo(address(_pool), 2570); + console.log("After removeCollateral(296695924278944779257290397234298756, 2570)"); + console.log("============"); + console.log("Quote Tokens -->", quote); + console.log("Collateral Tokens -->", collateral); + console.log("Lps -->", lps); + console.log("Exchange Rate-->", exchangeRate); + console.log("============"); + require(previousExchangeRate == exchangeRate, "Incorrect exchange rate"); + } + + function test_exchange_rate_bug2() external { + uint256 previousExchangeRate = 1e18; + _basicPoolHandler.addQuoteToken(211670885988646987334214990781526025942, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 6894274025938223490357894120267612065037086600750070030707794233); + + ( , uint256 quote, uint256 collateral, uint256 lps, , uint256 exchangeRate) = _poolInfo.bucketInfo(address(_pool), 2570); + console.log("After addQuoteToken(211670885988646987334214990781526025942, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 6894274025938223490357894120267612065037086600750070030707794233)"); + console.log("============"); + console.log("Quote Tokens -->", quote); + console.log("Collateral Tokens -->", collateral); + console.log("Lps -->", lps); + console.log("Exchange Rate-->", exchangeRate); + console.log("============"); + require(previousExchangeRate == exchangeRate, "Incorrect exchange rate"); + previousExchangeRate = exchangeRate; + _basicPoolHandler.addCollateral(117281, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 2); + + ( , quote, collateral, lps, , exchangeRate) = _poolInfo.bucketInfo(address(_pool), 2570); + console.log("After addCollateral(117281, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 2)"); + console.log("============"); + console.log("Quote Tokens -->", quote); + console.log("Collateral Tokens -->", collateral); + console.log("Lps -->", lps); + console.log("Exchange Rate-->", exchangeRate); + console.log("============"); + require(previousExchangeRate == exchangeRate, "Incorrect exchange rate"); + previousExchangeRate = exchangeRate; + + _basicPoolHandler.removeCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639932, 12612911637698029036253737442696522, 115792089237316195423570985008687907853269984665640564039457584007913129639933); + + ( , quote, collateral, lps, , exchangeRate) = _poolInfo.bucketInfo(address(_pool), 2570); + console.log("After removeCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639932, 12612911637698029036253737442696522, 115792089237316195423570985008687907853269984665640564039457584007913129639933)"); + console.log("============"); + console.log("Quote Tokens -->", quote); + console.log("Collateral Tokens -->", collateral); + console.log("Lps -->", lps); + console.log("Exchange Rate-->", exchangeRate); + console.log("============"); + // require(previousExchangeRate == exchangeRate, "Incorrect exchange rate"); + previousExchangeRate = exchangeRate; + + _basicPoolHandler.removeCollateral(1, 1e36, 2570); + _basicPoolHandler.removeQuoteToken(1, 1e36, 2570); + + _basicPoolHandler.removeCollateral(2, 1e36, 2570); + _basicPoolHandler.removeQuoteToken(2, 1e36, 2570); + + ( , quote, collateral, lps, , exchangeRate) = _poolInfo.bucketInfo(address(_pool), 2570); + console.log("After removeCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639932, 12612911637698029036253737442696522, 115792089237316195423570985008687907853269984665640564039457584007913129639933)"); + console.log("============"); + console.log("Quote Tokens -->", quote); + console.log("Collateral Tokens -->", collateral); + console.log("Lps -->", lps); + console.log("Exchange Rate-->", exchangeRate); + console.log("============"); + require(previousExchangeRate == exchangeRate, "Incorrect exchange rate"); + + } + +} \ No newline at end of file diff --git a/tests/forge/ERC20Pool/regression/RegressionTestLiquidation.t.sol b/tests/forge/ERC20Pool/regression/RegressionTestLiquidation.t.sol new file mode 100644 index 000000000..29c6b6076 --- /dev/null +++ b/tests/forge/ERC20Pool/regression/RegressionTestLiquidation.t.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.14; + +import { LiquidationInvariant } from "../invariants/LiquidationInvariant.t.sol"; + +import '@std/console.sol'; + +contract RegressionTestLiquidation is LiquidationInvariant { + + function setUp() public override { + super.setUp(); + + } + + function test_regression_quote_token() external { + _liquidationPoolHandler.addQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639932, 3, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + + // check invariants hold true + invariant_quoteTokenBalance_QT1(); + } + + function test_arithmetic_overflow() external { + _liquidationPoolHandler.kickAuction(128942392769655840156268259377571235707684499808935108685525899532745, 9654010200996517229486923829624352823010316518405842367464881, 135622574118732106350824249104903); + _liquidationPoolHandler.addQuoteToken(3487, 871, 1654); + + // check invariants hold true + invariant_quoteTokenBalance_QT1(); + } + + function test_bucket_take_lps_bug() public { + _liquidationPoolHandler.removeQuoteToken(7033457611004217223271238592369692530886316746601644, 0, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + _liquidationPoolHandler.addQuoteToken(1, 20033186019073, 1); + _liquidationPoolHandler.bucketTake(0, 0, false, 2876997751); + + invariant_Lps_B1(); + } + + function test_interest_rate_bug() public { + _liquidationPoolHandler.bucketTake(18065045387666484532028539614323078235438354477798625297386607289, 14629545458306, true, 1738460279262663206365845078188769); + + invariant_interest_rate_I1(); + } + + function test_incorrect_no_of_borrowers() public { + _liquidationPoolHandler.moveQuoteToken(18178450611611937161732340858718395124120481640398450530303803, 0, 93537843531612826457318744802930982491, 15596313608676556633725998020226886686244513); + _liquidationPoolHandler.addCollateral(2208149704044082902772911545020934265, 340235628931125711729099234105522626267587665393753030264689924088, 2997844437211835697043096396926932785920355866486893005710984415271); + _liquidationPoolHandler.moveQuoteToken(56944009718062971164908977784993293, 737882204379007468599822110965749781465, 1488100463155679769353095066686506252, 11960033727528802202227468733333727294); + _liquidationPoolHandler.moveQuoteToken(47205392335275917691737183012282140599753693978176314740917, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 164043848691337333691028718232); + _liquidationPoolHandler.kickAuction(184206711567329609153924955630229148705869686378631519380021040314, 78351, 115792089237316195423570985008687907853269984665640564039457584007913129639933); + _liquidationPoolHandler.kickAuction(3, 199726916764352560035199423206927461876998880387108455962754538835220966553, 3); + _liquidationPoolHandler.removeQuoteToken(999999991828440064944955196599190431639924811, 2781559202773230142346489450532860130, 3000000005240421579956496007310960085855569344); + _liquidationPoolHandler.pullCollateral(48768502867710912107594904694036421700, 275047566877984818806178837359260100); + _liquidationPoolHandler.bucketTake(2, 115792089237316195423570985008687907853269984665640564039457584007913129639934, false, 8154570107391684241724530527782571978369827827856399749867491880); + _liquidationPoolHandler.removeCollateral(43733538637150108518954934566131291302796656384802361118757432084573, 1, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + _liquidationPoolHandler.addQuoteToken(1, 2, 2); + _liquidationPoolHandler.repayDebt(647805461526201272, 0); + _liquidationPoolHandler.kickAuction(1019259585194528028904148545812353964867041444572537077023497678982801, 58796345025472936970320, 131319002678489819637546489086162345032717166507611595521); + _liquidationPoolHandler.moveQuoteToken(2, 2, 0, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _liquidationPoolHandler.moveQuoteToken(6164937621056362865643346803975636714, 4, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 315548939052682258); + _liquidationPoolHandler.repayDebt(2987067394366841692658, 170206016570563384086766968869520628); + _liquidationPoolHandler.pledgeCollateral(3558446182295495994762049031, 0); + _liquidationPoolHandler.drawDebt(4525700839008283200312069904720925039, 3000000000753374912785563581177665475703155339); + _liquidationPoolHandler.kickAuction(1, 3559779948348618822016735773117619950447774, 218801416747720); + _liquidationPoolHandler.addQuoteToken(1469716416900282992357252011629715552, 13037214114647887147246343731476169800, 984665637618013480616943810604306792); + _liquidationPoolHandler.pullCollateral(438961419917818200942534689247815826455600131, 64633474453314038763068322072915580384442279897841981); + + invariant_auctions_A3_A4(); + } +} \ No newline at end of file diff --git a/tests/forge/ERC20Pool/regression/RegressionTestReserves.t.sol b/tests/forge/ERC20Pool/regression/RegressionTestReserves.t.sol new file mode 100644 index 000000000..53ed71d77 --- /dev/null +++ b/tests/forge/ERC20Pool/regression/RegressionTestReserves.t.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.14; + +import { ReserveInvariants } from "../invariants/ReserveInvariants.t.sol"; + +import '@std/console.sol'; + +contract RegressionTestReserve is ReserveInvariants { + + function setUp() public override { + super.setUp(); + } + + function _test_reserve_1() external { + (uint256 reserve, , , , ) = _poolInfo.poolReservesInfo(address(_pool)); + console.log("Initial Reserve -->", reserve); + + _reservePoolHandler.kickAuction(3833, 15167, 15812); + + (reserve, , , , ) = _poolInfo.poolReservesInfo(address(_pool)); + console.log("Reserve after kick --->", reserve); + _invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9(); + + + _reservePoolHandler.removeQuoteToken(3841, 5339, 3672); + + (reserve, , , , ) = _poolInfo.poolReservesInfo(address(_pool)); + console.log("Reserve after removeQuoteToken --->", reserve); + _invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9(); + } + + function _test_reserve_2() external { + (uint256 reserve, , , , ) = _poolInfo.poolReservesInfo(address(_pool)); + console.log("Initial Reserve -->", reserve); + + _reservePoolHandler.bucketTake(19730, 10740, false, 15745); + + (reserve, , , , ) = _poolInfo.poolReservesInfo(address(_pool)); + console.log("Reserve after bucketTake --->", reserve); + _invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9(); + + + _reservePoolHandler.addCollateral(14982, 18415, 2079); + + (reserve, , , , ) = _poolInfo.poolReservesInfo(address(_pool)); + console.log("Reserve after addCollateral --->", reserve); + _invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9(); + } +} \ No newline at end of file diff --git a/tests/forge/ERC721Pool/ERC721DSTestPlus.sol b/tests/forge/ERC721Pool/ERC721DSTestPlus.sol index 6d8bc97a8..964941451 100644 --- a/tests/forge/ERC721Pool/ERC721DSTestPlus.sol +++ b/tests/forge/ERC721Pool/ERC721DSTestPlus.sol @@ -5,8 +5,8 @@ import { ERC20 } from '@openzeppelin/contracts/token/ERC20/ERC20.sol'; import '@openzeppelin/contracts/token/ERC721/ERC721.sol'; import '@openzeppelin/contracts/utils/structs/EnumerableSet.sol'; -import { DSTestPlus } from '../utils/DSTestPlus.sol'; -import { NFTCollateralToken, Token } from '../utils/Tokens.sol'; +import { DSTestPlus } from '../utils/DSTestPlus.sol'; +import { NFTCollateralToken, Token, TokenWithNDecimals } from '../utils/Tokens.sol'; import { ERC721Pool } from 'src/ERC721Pool.sol'; import { ERC721PoolFactory } from 'src/ERC721PoolFactory.sol'; @@ -25,7 +25,7 @@ abstract contract ERC721DSTestPlus is DSTestPlus, IERC721PoolEvents { using EnumerableSet for EnumerableSet.UintSet; NFTCollateralToken internal _collateral; - Token internal _quote; + TokenWithNDecimals internal _quote; ERC20 internal _ajnaToken; mapping(uint256 => uint256) NFTidToIndex; @@ -100,13 +100,12 @@ abstract contract ERC721DSTestPlus is DSTestPlus, IERC721PoolEvents { { uint256 fractionOfNftRemaining = lpsAsCollateral % 1e18; assertLt(fractionOfNftRemaining, 1e18); - - // 1 wei workaround due to rounding - depositRequired = Maths.wmul(1e18 - fractionOfNftRemaining + 1, price); + + depositRequired = Maths.wmul(1e18 - fractionOfNftRemaining, price); } deal(_pool.quoteTokenAddress(), lender, depositRequired); Token(_pool.quoteTokenAddress()).approve(address(_pool) , depositRequired); - _pool.addQuoteToken(depositRequired, bucketIndex); + _pool.addQuoteToken(depositRequired, bucketIndex, block.timestamp + 1 minutes); (lenderLpBalance, ) = _pool.lenderInfo(bucketIndex, lender); lpsAsCollateral = _poolUtils.lpsToCollateral(address(_pool), lenderLpBalance, bucketIndex); } @@ -175,7 +174,7 @@ abstract contract ERC721DSTestPlus is DSTestPlus, IERC721PoolEvents { emit Transfer(from, address(_pool), tokenIds[i]); } - lps_ = ERC721Pool(address(_pool)).addCollateral(tokenIds, index); + lps_ = ERC721Pool(address(_pool)).addCollateral(tokenIds, index, block.timestamp + 10 minutes); for (uint256 i = 0; i < tokenIds.length; i++) { assertEq(_collateral.ownerOf(tokenIds[i]), address(_pool)); // token is owned by pool after add @@ -336,7 +335,7 @@ abstract contract ERC721DSTestPlus is DSTestPlus, IERC721PoolEvents { emit RepayDebt(borrower, amountRepaid, collateralToPull, newLup); } - ERC721Pool(address(_pool)).repayDebt(borrower, amountToRepay, collateralToPull); + ERC721Pool(address(_pool)).repayDebt(borrower, amountToRepay, collateralToPull, borrower, MAX_FENWICK_INDEX); // post pull checks if (collateralToPull != 0) { @@ -352,7 +351,7 @@ abstract contract ERC721DSTestPlus is DSTestPlus, IERC721PoolEvents { emit RepayDebt(borrower, amountRepaid, collateralToPull, newLup); } - ERC721Pool(address(_pool)).repayDebt(borrower, amountToRepay, collateralToPull); + ERC721Pool(address(_pool)).repayDebt(borrower, amountToRepay, collateralToPull, borrower, MAX_FENWICK_INDEX); } } @@ -430,10 +429,42 @@ abstract contract ERC721DSTestPlus is DSTestPlus, IERC721PoolEvents { } } + function _assertCollateralInvariants() internal { + uint256 collateralInBuckets; + for(uint256 bucketIndex = 0; bucketIndex <= 7388; bucketIndex++) { + (, uint256 bucketCollateral, , , ) = _pool.bucketInfo(bucketIndex); + collateralInBuckets += bucketCollateral; + } + + uint256 borrowersCollateral; + for (uint256 i = 0; i < borrowers.length(); i++) { + (, uint256 borrowerCollateral, ) = _poolUtils.borrowerInfo(address(_pool), borrowers.at(i)); + borrowersCollateral += borrowerCollateral; + } + + // pool pledged collateral accumulator should be equal with the amounts of collateral owned by borrowers + assertEq(borrowersCollateral, _pool.pledgedCollateral()); + + // collateral in buckets + collateral owned borrowers should be equal with the total number of tokens owned by the pool + uint256 poolBalance = _collateral.balanceOf(address(_pool)); + assertEq(collateralInBuckets + borrowersCollateral, poolBalance * 1e18); + } + /**********************/ /*** Revert asserts ***/ /**********************/ + function _assertAddCollateralExpiredRevert( + address from, + uint256[] memory tokenIds, + uint256 index, + uint256 expiry + ) internal { + changePrank(from); + vm.expectRevert(IPoolErrors.TransactionExpired.selector); + ERC721Pool(address(_pool)).addCollateral(tokenIds, index, expiry); + } + function _assertDeployWith0xAddressRevert( address poolFactory, address collateral, @@ -504,11 +535,22 @@ abstract contract ERC721DSTestPlus is DSTestPlus, IERC721PoolEvents { uint256 indexLimit ) internal override { changePrank(from); - vm.expectRevert(IPoolErrors.LimitIndexReached.selector); + vm.expectRevert(IPoolErrors.LimitIndexExceeded.selector); uint256[] memory emptyArray; ERC721Pool(address(_pool)).drawDebt(from, amount, indexLimit, emptyArray); } + function _assertMergeRemoveCollateralAuctionNotClearedRevert( + address from, + uint256 toIndex, + uint256 noOfNFTsToRemove, + uint256[] memory removeCollateralAtIndex + ) internal { + changePrank(from); + vm.expectRevert(abi.encodeWithSignature('AuctionNotCleared()')); + ERC721Pool(address(_pool)).mergeOrRemoveCollateral(removeCollateralAtIndex, noOfNFTsToRemove, toIndex); + } + function _assertCannotMergeToHigherPriceRevert( address from, uint256 toIndex, @@ -531,6 +573,17 @@ abstract contract ERC721DSTestPlus is DSTestPlus, IERC721PoolEvents { ERC721Pool(address(_pool)).drawDebt(from, amount, indexLimit, emptyArray); } + function _assertBorrowDustRevert( + address from, + uint256 amount, + uint256 indexLimit + ) internal { + changePrank(from); + vm.expectRevert(IPoolErrors.DustAmountNotExceeded.selector); + uint256[] memory emptyArray; + ERC721Pool(address(_pool)).drawDebt(from, amount, indexLimit, emptyArray); + } + function _assertBorrowMinDebtRevert( address from, uint256 amount, @@ -548,7 +601,17 @@ abstract contract ERC721DSTestPlus is DSTestPlus, IERC721PoolEvents { ) internal { changePrank(from); vm.expectRevert(IPoolErrors.InsufficientCollateral.selector); - ERC721Pool(address(_pool)).repayDebt(from, 0, amount); + ERC721Pool(address(_pool)).repayDebt(from, 0, amount, from, MAX_FENWICK_INDEX); + } + + function _assertPullLimitIndexRevert( + address from, + uint256 amount, + uint256 indexLimit + ) internal { + changePrank(from); + vm.expectRevert(IPoolErrors.LimitIndexExceeded.selector); + ERC721Pool(address(_pool)).repayDebt(from, 0, amount, from, indexLimit); } function _assertRepayNoDebtRevert( @@ -558,7 +621,7 @@ abstract contract ERC721DSTestPlus is DSTestPlus, IERC721PoolEvents { ) internal { changePrank(from); vm.expectRevert(IPoolErrors.NoDebt.selector); - ERC721Pool(address(_pool)).repayDebt(borrower, amount, 0); + ERC721Pool(address(_pool)).repayDebt(borrower, amount, 0, borrower, MAX_FENWICK_INDEX); } function _assertRepayMinDebtRevert( @@ -568,7 +631,7 @@ abstract contract ERC721DSTestPlus is DSTestPlus, IERC721PoolEvents { ) internal { changePrank(from); vm.expectRevert(IPoolErrors.AmountLTMinDebt.selector); - ERC721Pool(address(_pool)).repayDebt(borrower, amount, 0); + ERC721Pool(address(_pool)).repayDebt(borrower, amount, 0, borrower, MAX_FENWICK_INDEX); } function _assertRemoveCollateralNoClaimRevert( @@ -603,7 +666,7 @@ abstract contract ERC721HelperContract is ERC721DSTestPlus { _collateral = new NFTCollateralToken(); vm.makePersistent(address(_collateral)); - _quote = new Token("Quote", "Q"); + _quote = new TokenWithNDecimals("Quote", "Q", 18); vm.makePersistent(address(_quote)); _ajnaToken = ERC20(_ajna); vm.makePersistent(_ajna); @@ -670,6 +733,65 @@ abstract contract ERC721HelperContract is ERC721DSTestPlus { } } +abstract contract ERC721NDecimalsHelperContract is ERC721DSTestPlus { + using EnumerableSet for EnumerableSet.AddressSet; + ERC721PoolFactory internal _poolFactory; + + constructor(uint8 decimals) { + vm.createSelectFork(vm.envString("ETH_RPC_URL")); + + _collateral = new NFTCollateralToken(); + vm.makePersistent(address(_collateral)); + _quote = new TokenWithNDecimals("Quote", "Q", decimals); + vm.makePersistent(address(_quote)); + _ajnaToken = ERC20(_ajna); + vm.makePersistent(_ajna); + _poolUtils = new PoolInfoUtils(); + vm.makePersistent(address(_poolUtils)); + _poolFactory = new ERC721PoolFactory(_ajna); + vm.makePersistent(address(_poolFactory)); + + _startTime = block.timestamp; + uint256[] memory tokenIds; + address contractAddress = _poolFactory.deployPool(address(_collateral), address(_quote), tokenIds, 0.05 * 10**18); + vm.makePersistent(contractAddress); + _pool = ERC721Pool(contractAddress); + } + + function _mintAndApproveQuoteTokens(address operator_, uint256 mintAmount_) internal { + deal(address(_quote), operator_, mintAmount_); + vm.prank(operator_); + _quote.approve(address(_pool), type(uint256).max); + } + + function _mintAndApproveCollateralTokens(address operator_, uint256 mintAmount_) internal { + _collateral.mint(operator_, mintAmount_); + vm.prank(operator_); + _collateral.setApprovalForAll(address(_pool), true); + } + + /** + * @dev Creates debt for an anonymous non-player borrower not otherwise involved in the test. + **/ + function _anonBorrowerDrawsDebt(uint256 loanAmount) internal { + // _anonBorrowerCount += 1; + + address borrower = makeAddr(string(abi.encodePacked("anonBorrower", borrowers.length()))); + vm.stopPrank(); + _mintAndApproveCollateralTokens(borrower, 1); + uint256[] memory tokenIdsToAdd = new uint256[](1); + tokenIdsToAdd[0] = _collateral.totalSupply(); + + _drawDebtNoLupCheck({ + from: borrower, + borrower: borrower, + amountToBorrow: loanAmount, + limitIndex: MAX_FENWICK_INDEX, + tokenIds: tokenIdsToAdd + }); + } +} + abstract contract ERC721FuzzyHelperContract is ERC721DSTestPlus { uint256 public constant LARGEST_AMOUNT = type(uint256).max / 10**27; @@ -678,7 +800,7 @@ abstract contract ERC721FuzzyHelperContract is ERC721DSTestPlus { constructor() { _collateral = new NFTCollateralToken(); - _quote = new Token("Quote", "Q"); + _quote = new TokenWithNDecimals("Quote", "Q", 18); _ajnaToken = ERC20(_ajna); _poolUtils = new PoolInfoUtils(); _poolFactory = new ERC721PoolFactory(_ajna); diff --git a/tests/forge/ERC721Pool/ERC721NonStandardTokenTransfer.sol b/tests/forge/ERC721Pool/ERC721NonStandardTokenTransfer.sol deleted file mode 100644 index 82c16794b..000000000 --- a/tests/forge/ERC721Pool/ERC721NonStandardTokenTransfer.sol +++ /dev/null @@ -1,121 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.14; - -import { ERC721HelperContract } from './ERC721DSTestPlus.sol'; - -import 'src/ERC721Pool.sol'; -import 'src/ERC721PoolFactory.sol'; - -interface ICryptoFighters { - function transferFrom(address from_, address to_, uint256 tokenId_) external; - function transfer(address to_, uint256 tokenId_) external; - function approve(address to_, uint256 tokenId_) external; - function ownerOf(uint256 tokenId_) external returns(address); - function fighterIndexToApproved(uint256 tokenId_) external returns(address); -} - -contract ERC721PoolNonStandardNftTest is ERC721HelperContract { - address internal _borrower; - address cryptoKittiesAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d; - ICryptoKitties cryptoKittiesContract = ICryptoKitties(cryptoKittiesAddress); - address cryptoFightersAddress = 0x87d598064c736dd0C712D329aFCFAA0Ccc1921A1; - ICryptoFighters cryptoFightersContract = ICryptoFighters(cryptoFightersAddress); - address cryptoPunksAddress = 0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB; - ICryptoPunks cryptoPunksContract = ICryptoPunks(cryptoPunksAddress); - - function setUp() external { - // Borrower has Crypto Kitties/Fighters/Punks in his wallet at specified block - vm.createSelectFork(vm.envString("ETH_RPC_URL"), 16167859); - } - - function testCryptoKittiesNftTransfer() external { - uint256[] memory tokenIds; - _pool = ERC721Pool(new ERC721PoolFactory(_ajna).deployPool(cryptoKittiesAddress, address(_quote), tokenIds, 0.05 * 10**18)); - - _borrower = 0xDAd45d13472568181B83A731D1e170f4c6e95a83; - changePrank(_borrower); - - assertEq(cryptoKittiesContract.ownerOf(1777317), _borrower); - // Borrower approves Nft - cryptoKittiesContract.approve(address(_pool), 1777317); - - assertEq(cryptoKittiesContract.kittyIndexToApproved(1777317), address(_pool)); - - tokenIds = new uint256[](1); - tokenIds[0] = 1777317; - - // Pledge Collateral - ERC721Pool(address(_pool)).drawDebt(_borrower, 0, 0, tokenIds); - - // Check Pool is owner of NFT - assertEq(cryptoKittiesContract.ownerOf(1777317), address(_pool)); - - // Pull collateral - ERC721Pool(address(_pool)).repayDebt(_borrower, 0, 1); - - // Check Borrower is owner of NFT - assertEq(cryptoKittiesContract.ownerOf(1777317), _borrower); - - } - - function testCryptoFightersNftTransfer() external { - uint256[] memory tokenIds; - _pool = ERC721Pool(new ERC721PoolFactory(_ajna).deployPool(cryptoFightersAddress, address(_quote), tokenIds, 0.05 * 10**18)); - - _borrower = 0x4E2EAE6ABA4E61Eb16c2D7905a4747323Ca7a504; - changePrank(_borrower); - - assertEq(cryptoFightersContract.ownerOf(1), _borrower); - // Borrower approves Nft - cryptoFightersContract.approve(address(_pool), 1); - - assertEq(cryptoFightersContract.fighterIndexToApproved(1), address(_pool)); - - tokenIds = new uint256[](1); - tokenIds[0] = 1; - - // Pledge Collateral - ERC721Pool(address(_pool)).drawDebt(_borrower, 0, 0, tokenIds); - - // Check Pool is owner of NFT - assertEq(cryptoFightersContract.ownerOf(1), address(_pool)); - - // Pull collateral - ERC721Pool(address(_pool)).repayDebt(_borrower, 0, 1); - - // Check Borrower is owner of NFT - assertEq(cryptoFightersContract.ownerOf(1), _borrower); - - } - - function testCryptoPunksNftTransfer() external { - uint256[] memory tokenIds; - _pool = ERC721Pool(new ERC721PoolFactory(_ajna).deployPool(cryptoPunksAddress, address(_quote), tokenIds, 0.05 * 10**18)); - - - _borrower = 0xB88F61E6FbdA83fbfffAbE364112137480398018; - changePrank(_borrower); - - // Check Borrower is owner of NFT - assertEq(cryptoPunksContract.punkIndexToAddress(1), _borrower); - - // Borrower approves Nft - cryptoPunksContract.offerPunkForSaleToAddress(1, 0, address(_pool)); - - tokenIds = new uint256[](1); - tokenIds[0] = 1; - - // Pledge Collateral - ERC721Pool(address(_pool)).drawDebt(_borrower, 0, 0, tokenIds); - - // Check Pool is owner of NFT - assertEq(cryptoPunksContract.punkIndexToAddress(1), address(_pool)); - - // Pull collateral - ERC721Pool(address(_pool)).repayDebt(_borrower, 0, 1); - - // Check Borrower is owner of NFT - assertEq(cryptoPunksContract.punkIndexToAddress(1), _borrower); - - } -} diff --git a/tests/forge/ERC721Pool/ERC721PoolBorrow.t.sol b/tests/forge/ERC721Pool/ERC721PoolBorrow.t.sol index 806ffc285..04ae37064 100644 --- a/tests/forge/ERC721Pool/ERC721PoolBorrow.t.sol +++ b/tests/forge/ERC721Pool/ERC721PoolBorrow.t.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.14; -import { ERC721HelperContract, ERC721FuzzyHelperContract } from './ERC721DSTestPlus.sol'; +import { ERC721HelperContract, ERC721FuzzyHelperContract, ERC721NDecimalsHelperContract } from './ERC721DSTestPlus.sol'; import 'src/ERC721Pool.sol'; import 'src/libraries/internal/Maths.sol'; -import { MAX_PRICE, _priceAt } from 'src/libraries/helpers/PoolHelper.sol'; +import { MAX_FENWICK_INDEX, MAX_PRICE, _priceAt } from 'src/libraries/helpers/PoolHelper.sol'; abstract contract ERC721PoolBorrowTest is ERC721HelperContract { address internal _borrower; @@ -59,127 +59,101 @@ contract ERC721SubsetPoolBorrowTest is ERC721PoolBorrowTest { function testBorrowLimitReached() external tearDown { // lender deposits 10000 Quote into 3 buckets - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2550 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2551 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2552 - } - ); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2550 + }); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2551 + }); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2552 + }); // borrower deposits three NFTs into the subset pool uint256[] memory tokenIdsToAdd = new uint256[](3); tokenIdsToAdd[0] = 1; tokenIdsToAdd[1] = 3; tokenIdsToAdd[2] = 5; - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - tokenIds: tokenIdsToAdd - } - ); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + tokenIds: tokenIdsToAdd + }); // should revert if insufficient quote available before limit price - _assertBorrowLimitIndexRevert( - { - from: _borrower, - amount: 21_000 * 1e18, - indexLimit: 2551 - } - ); + _assertBorrowLimitIndexRevert({ + from: _borrower, + amount: 21_000 * 1e18, + indexLimit: 2551 + }); } function testBorrowBorrowerUnderCollateralized() external tearDown { // add initial quote to the pool - _addInitialLiquidity( - { - from: _lender, - amount: 1_000 * 1e18, - index: 3575 - } - ); + _addInitialLiquidity({ + from: _lender, + amount: 1_000 * 1e18, + index: 3575 + }); // borrower pledges some collateral uint256[] memory tokenIdsToAdd = new uint256[](2); tokenIdsToAdd[0] = 5; tokenIdsToAdd[1] = 3; - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - tokenIds: tokenIdsToAdd - } - ); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + tokenIds: tokenIdsToAdd + }); // should revert if borrower did not deposit enough collateral - _assertBorrowBorrowerUnderCollateralizedRevert( - { - from: _borrower, - amount: 40 * 1e18, - indexLimit: 4000 - } - ); + _assertBorrowBorrowerUnderCollateralizedRevert({ + from: _borrower, + amount: 40 * 1e18, + indexLimit: 4000 + }); } function testBorrowPoolUnderCollateralized() external tearDown { // add initial quote to the pool - _addInitialLiquidity( - { - from: _lender, - amount: 1_000 * 1e18, - index: 3232 - } - ); + _addInitialLiquidity({ + from: _lender, + amount: 1_000 * 1e18, + index: 3232 + }); // should revert if borrow would result in pool under collateralization - _assertBorrowBorrowerUnderCollateralizedRevert( - { - from: _borrower, - amount: 500, - indexLimit: 4000 - } - ); + _assertBorrowBorrowerUnderCollateralizedRevert({ + from: _borrower, + amount: 500, + indexLimit: 4000 + }); } function testBorrowAndRepay() external { // lender deposits 10000 Quote into 3 buckets - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2550 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2551 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2552 - } - ); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2550 + }); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2551 + }); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2552 + }); // check initial token balances assertEq(_collateral.balanceOf(_borrower), 52); @@ -206,40 +180,33 @@ contract ERC721SubsetPoolBorrowTest is ERC721PoolBorrowTest { interestRateUpdate: _startTime }) ); - // check initial bucket state - _assertBucket( - { - index: 2550, - lpBalance: 10_000 * 1e27, - collateral: 0, - deposit: 10_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); + _assertBucket({ + index: 2550, + lpBalance: 10_000 * 1e18, + collateral: 0, + deposit: 10_000 * 1e18, + exchangeRate: 1 * 1e18 + }); // borrower deposits three NFTs into the subset pool uint256[] memory tokenIdsToAdd = new uint256[](3); tokenIdsToAdd[0] = 1; tokenIdsToAdd[1] = 3; tokenIdsToAdd[2] = 5; - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - tokenIds: tokenIdsToAdd - } - ); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + tokenIds: tokenIdsToAdd + }); // borrower borrows from the pool uint256 borrowAmount = 3_000 * 1e18; - _borrow( - { - from: _borrower, - amount: borrowAmount, - indexLimit: 2551, - newLup: _priceAt(2550) - } - ); + _borrow({ + from: _borrower, + amount: borrowAmount, + indexLimit: 2551, + newLup: _priceAt(2550) + }); // check token balances after borrow assertEq(_collateral.balanceOf(_borrower), 49); @@ -266,28 +233,22 @@ contract ERC721SubsetPoolBorrowTest is ERC721PoolBorrowTest { interestRateUpdate: _startTime }) ); - // check bucket state after borrow - _assertBucket( - { - index: 2550, - lpBalance: 10_000 * 1e27, - collateral: 0, - deposit: 10_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); - + _assertBucket({ + index: 2550, + lpBalance: 10_000 * 1e18, + collateral: 0, + deposit: 10_000 * 1e18, + exchangeRate: 1 * 1e18 + }); // check borrower info after borrow - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 3_002.884615384615386000 * 1e18, - borrowerCollateral: 3 * 1e18, - borrowert0Np: 1_051.009615384615385100 * 1e18, - borrowerCollateralization: 3.007999714779824033 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 3_002.884615384615386000 * 1e18, + borrowerCollateral: 3 * 1e18, + borrowert0Np: 1_051.009615384615385100 * 1e18, + borrowerCollateralization: 3.007999714779824033 * 1e18 + }); // pass time to allow interest to accumulate skip(10 days); @@ -328,40 +289,33 @@ contract ERC721SubsetPoolBorrowTest is ERC721PoolBorrowTest { }) ); // check bucket state after partial repay - _assertBucket( - { - index: 2550, - lpBalance: 10_000 * 1e27, - collateral: 0, - deposit: 10_001.17341179741568 * 1e18, - exchangeRate: 1.000117341179741568 * 1e27 - } - ); - + _assertBucket({ + index: 2550, + lpBalance: 10_000 * 1e18, + collateral: 0, + deposit: 10_001.17341179741568 * 1e18, + exchangeRate: 1.000117341179741568 * 1e18 + }); // check borrower info after partial repay - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 1_507.000974734143274062 * 1e18, - borrowerCollateral: 3 * 1e18, - borrowert0Np: 1_051.009615384615385100 * 1e18, - borrowerCollateralization: 5.993809040625961846 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 1_507.000974734143274062 * 1e18, + borrowerCollateral: 3 * 1e18, + borrowert0Np: 1_051.009615384615385100 * 1e18, + borrowerCollateralization: 5.993809040625961846 * 1e18 + }); // pass time to allow additional interest to accumulate skip(10 days); // find pending debt after interest accumulation - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 1_508.860066921599065131 * 1e18, - borrowerCollateral: 3 * 1e18, - borrowert0Np: 1_051.009615384615385100 * 1e18, - borrowerCollateralization: 5.986423966420065589 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 1_508.860066921599065131 * 1e18, + borrowerCollateral: 3 * 1e18, + borrowert0Np: 1_051.009615384615385100 * 1e18, + borrowerCollateralization: 5.986423966420065589 * 1e18 + }); // mint additional quote to allow borrower to repay their loan plus interest deal(address(_quote), _borrower, _quote.balanceOf(_borrower) + 1_000 * 1e18); @@ -409,33 +363,26 @@ contract ERC721SubsetPoolBorrowTest is ERC721PoolBorrowTest { interestRateUpdate: _startTime + 20 days }) ); - _assertEMAs( - { - debtEma: 142.074529848655991542 * 1e18, - lupColEma: 851.567601449557349751 * 1e18 - } - ); - + _assertEMAs({ + debtEma: 142.074529848655991542 * 1e18, + lupColEma: 851.567601449557349751 * 1e18 + }); // check bucket state after fully repay - _assertBucket( - { - index: 2550, - lpBalance: 10_000 * 1e27, - collateral: 0, - deposit: 10_001.70173768409813 * 1e18, - exchangeRate: 1.000170173768409813 * 1e27 - } - ); + _assertBucket({ + index: 2550, + lpBalance: 10_000 * 1e18, + collateral: 0, + deposit: 10_001.70173768409813 * 1e18, + exchangeRate: 1.000170173768409813 * 1e18 + }); // check borrower info after fully repay - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 0, - borrowerCollateral: 0, - borrowert0Np: 0, - borrowerCollateralization: 1 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 0, + borrowerCollateral: 0, + borrowert0Np: 0, + borrowerCollateralization: 1 * 1e18 + }); assertEq(_collateral.balanceOf(_borrower), 52); assertEq(_collateral.balanceOf(address(_pool)), 0); @@ -443,86 +390,77 @@ contract ERC721SubsetPoolBorrowTest is ERC721PoolBorrowTest { function testPoolRepayRequireChecks() external tearDown { // add initial quote to the pool - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2550 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2551 - } - ); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2550 + }); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2551 + }); deal(address(_quote), _borrower, _quote.balanceOf(_borrower) + 10_000 * 1e18); + // should revert if borrower has no debt - _assertRepayNoDebtRevert( - { - from: _borrower, - borrower: _borrower, - amount: 10_000 * 1e18 - } - ); + _assertRepayNoDebtRevert({ + from: _borrower, + borrower: _borrower, + amount: 10_000 * 1e18 + }); // borrower 1 borrows 1000 quote from the pool uint256[] memory tokenIdsToAdd = new uint256[](3); tokenIdsToAdd[0] = 1; tokenIdsToAdd[1] = 3; tokenIdsToAdd[2] = 5; - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - tokenIds: tokenIdsToAdd - } - ); - _borrow( - { - from: _borrower, - amount: 1_000 * 1e18, - indexLimit: 3_000, - newLup: 3_010.892022197881557845 * 1e18 - } - ); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + tokenIds: tokenIdsToAdd + }); + _borrow({ + from: _borrower, + amount: 1_000 * 1e18, + indexLimit: 3_000, + newLup: 3_010.892022197881557845 * 1e18 + }); - _assertLoans( - { - noOfLoans: 1, - maxBorrower: _borrower, - maxThresholdPrice: 333.653846153846154 * 1e18 - } - ); + _assertLoans({ + noOfLoans: 1, + maxBorrower: _borrower, + maxThresholdPrice: 333.653846153846154 * 1e18 + }); + + // should revert if LUP is below the limit + ( , , , , , uint256 lupIndex ) = _poolUtils.poolPricesInfo(address(_pool)); + _assertPullLimitIndexRevert({ + from: _borrower, + amount: 2, + indexLimit: lupIndex - 1 + }); // borrower 2 borrows 3k quote from the pool and becomes new queue HEAD tokenIdsToAdd = new uint256[](1); tokenIdsToAdd[0] = 53; - _pledgeCollateral( - { - from: _borrower2, - borrower: _borrower2, - tokenIds: tokenIdsToAdd - } - ); - _borrow( - { - from: _borrower2, - amount: 3_000 * 1e18, - indexLimit: 3_000, - newLup: 3_010.892022197881557845 * 1e18 - } - ); + _pledgeCollateral({ + from: _borrower2, + borrower: _borrower2, + tokenIds: tokenIdsToAdd + }); + _borrow({ + from: _borrower2, + amount: 3_000 * 1e18, + indexLimit: 3_000, + newLup: 3_010.892022197881557845 * 1e18 + }); - _assertLoans( - { - noOfLoans: 2, - maxBorrower: _borrower2, - maxThresholdPrice: 3_002.884615384615386 * 1e18 - } - ); + _assertLoans({ + noOfLoans: 2, + maxBorrower: _borrower2, + maxThresholdPrice: 3_002.884615384615386 * 1e18 + }); // should be able to repay loan if properly specified _repayDebt({ @@ -537,49 +475,39 @@ contract ERC721SubsetPoolBorrowTest is ERC721PoolBorrowTest { function testRepayLoanFromDifferentActor() external tearDown { - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2550 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2551 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2552 - } - ); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2550 + }); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2551 + }); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2552 + }); // borrower deposits three NFTs into the subset pool uint256[] memory tokenIdsToAdd = new uint256[](3); tokenIdsToAdd[0] = 1; tokenIdsToAdd[1] = 3; tokenIdsToAdd[2] = 5; - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - tokenIds: tokenIdsToAdd - } - ); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + tokenIds: tokenIdsToAdd + }); // borrower borrows from the pool - _borrow( - { - from: _borrower, - amount: 3_000 * 1e18, - indexLimit: 2_551, - newLup: 3_010.892022197881557845 * 1e18 - } - ); + _borrow({ + from: _borrower, + amount: 3_000 * 1e18, + indexLimit: 2_551, + newLup: 3_010.892022197881557845 * 1e18 + }); // check token balances after borrow assertEq(_pool.pledgedCollateral(), Maths.wad(3)); @@ -616,71 +544,54 @@ contract ERC721SubsetPoolBorrowTest is ERC721PoolBorrowTest { } } -contract ERC721CollectionPoolBorrowTest is ERC721PoolBorrowTest { - uint internal _anonBorrowerCount = 0; +contract ERC721CollectionPoolBorrowTest is ERC721NDecimalsHelperContract(18) { + address internal _borrower; + address internal _lender; - function createPool() external override returns (ERC721Pool) { - return _deployCollectionPool(); - } + function setUp() external { + _borrower = makeAddr("borrower"); + _lender = makeAddr("lender"); - /** - * @dev Creates debt for an anonymous non-player borrower not otherwise involved in the test. - **/ - function _anonBorrowerDrawsDebt(uint256 loanAmount) internal { - _anonBorrowerCount += 1; - address borrower = makeAddr(string(abi.encodePacked("anonBorrower", _anonBorrowerCount))); - vm.stopPrank(); - _mintAndApproveCollateralTokens(borrower, 1); - uint256[] memory tokenIdsToAdd = new uint256[](1); - tokenIdsToAdd[0] = _collateral.totalSupply(); - - _drawDebtNoLupCheck( - { - from: borrower, - borrower: borrower, - amountToBorrow: loanAmount, - limitIndex: 7_777, - tokenIds: tokenIdsToAdd - } - ); + _mintAndApproveQuoteTokens(_lender, 200_000 * 1e18); + _mintAndApproveCollateralTokens(_borrower, 52); + + vm.prank(_borrower); + _quote.approve(address(_pool), 200_000 * 1e18); } function testMinBorrowAmountCheck() external tearDown { // add initial quote to the pool changePrank(_lender); - _pool.addQuoteToken(20_000 * 1e18, 2550); + _pool.addQuoteToken(20_000 * 1e18, 2550, block.timestamp + 1 minutes); // 10 borrowers draw debt for (uint i=0; i<10; ++i) { _anonBorrowerDrawsDebt(1_200 * 1e18); } + (, uint256 loansCount, , , ) = _poolUtils.poolLoansInfo(address(_pool)); assertEq(loansCount, 10); uint256[] memory tokenIdsToAdd = new uint256[](1); tokenIdsToAdd[0] = 5; - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - tokenIds: tokenIdsToAdd - } - ); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + tokenIds: tokenIdsToAdd + }); // should revert if borrower attempts to borrow more than minimum amount - _assertBorrowMinDebtRevert( - { - from: _borrower, - amount: 100 * 1e18, - indexLimit: 7_777 - } - ); + _assertBorrowMinDebtRevert({ + from: _borrower, + amount: 100 * 1e18, + indexLimit: MAX_FENWICK_INDEX + }); } function testMinRepayAmountCheck() external tearDown { // add initial quote to the pool changePrank(_lender); - _pool.addQuoteToken(20_000 * 1e18, 2550); + _pool.addQuoteToken(20_000 * 1e18, 2550, block.timestamp + 1 minutes); // 9 other borrowers draw debt for (uint i=0; i<9; ++i) { @@ -693,31 +604,74 @@ contract ERC721CollectionPoolBorrowTest is ERC721PoolBorrowTest { tokenIdsToAdd[1] = 3; tokenIdsToAdd[2] = 5; - _drawDebtNoLupCheck( - { - from: _borrower, - borrower: _borrower, - amountToBorrow: 1_000 * 1e18, - limitIndex: 3000, - tokenIds: tokenIdsToAdd - } - ); + _drawDebtNoLupCheck({ + from: _borrower, + borrower: _borrower, + amountToBorrow: 1_000 * 1e18, + limitIndex: 3000, + tokenIds: tokenIdsToAdd + }); (, uint256 loansCount, , , ) = _poolUtils.poolLoansInfo(address(_pool)); assertEq(loansCount, 10); // should revert if amount left after repay is less than the average debt - _assertRepayMinDebtRevert( - { - from: _borrower, - borrower: _borrower, - amount: 900 * 1e18 - } - ); + _assertRepayMinDebtRevert({ + from: _borrower, + borrower: _borrower, + amount: 900 * 1e18 + }); + } +} + +contract ERC721ScaledQuoteTokenBorrowTest is ERC721NDecimalsHelperContract(4) { + address internal _borrower; + address internal _lender; + + function setUp() external { + _borrower = makeAddr("borrower"); + _lender = makeAddr("lender"); + + _mintAndApproveQuoteTokens(_lender, 20_000 * 1e4); + _mintAndApproveCollateralTokens(_borrower, 5); } + function testMinDebtBelowDustLimitCheck() external tearDown { + // add initial quote to the pool + changePrank(_lender); + _pool.addQuoteToken(20_000 * 1e18, 2550, block.timestamp + 30); + + // borrower pledges a single NFT + uint256[] memory tokenIdsToAdd = new uint256[](1); + tokenIdsToAdd[0] = 5; + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + tokenIds: tokenIdsToAdd + }); + + // should revert if borrower tries to draw debt below dust limit + _assertBorrowDustRevert({ + from: _borrower, + amount: 0.00005 * 1e18, + indexLimit: 2550 + }); + + // 10 borrowers draw debt at the dust limit + for (uint i=0; i<10; ++i) { + _anonBorrowerDrawsDebt(0.0001 * 1e18); + } + + // should still revert if borrower tries to draw debt below dust limit + _assertBorrowDustRevert({ + from: _borrower, + amount: 0.000075 * 1e18, + indexLimit: 2550 + }); + } } + contract ERC721PoolBorrowFuzzyTest is ERC721FuzzyHelperContract { address internal _borrower; @@ -754,22 +708,23 @@ contract ERC721PoolBorrowFuzzyTest is ERC721FuzzyHelperContract { uint256[] memory indexes = new uint256[](numIndexes); for (uint256 i = 0; i < numIndexes; ++i) { deal(address(_quote), _lender, mintAmount_); + indexes[i] = _randomIndexWithMinimumPrice(5000); // setting a minimum price for collateral prevents exceeding memory and gas limits _addLiquidity({ from: _lender, amount: mintAmount_, index: indexes[i], - lpAward: mintAmount_ * 1e9, + lpAward: mintAmount_, newLup: _calculateLup(address(_pool), 0) }); _assertBucket({ index: indexes[i], - lpBalance: mintAmount_ * 1e9, + lpBalance: mintAmount_, collateral: 0, deposit: mintAmount_, - exchangeRate: 1e27 + exchangeRate: 1e18 }); } @@ -792,10 +747,10 @@ contract ERC721PoolBorrowFuzzyTest is ERC721FuzzyHelperContract { for (uint256 i = 0; i < numIndexes; ++i) { _assertBucket({ index: indexes[i], - lpBalance: mintAmount_ * 1e9, + lpBalance: mintAmount_, collateral: 0, deposit: mintAmount_, - exchangeRate: 1e27 + exchangeRate: 1e18 }); } @@ -832,6 +787,7 @@ contract ERC721PoolBorrowFuzzyTest is ERC721FuzzyHelperContract { // repay all debt and withdraw collateral (debt, , ) = _poolUtils.borrowerInfo(address(_pool), address(_borrower)); deal(address(_quote), _borrower, debt); + _repayDebt({ from: _borrower, borrower: _borrower, @@ -848,16 +804,17 @@ contract ERC721PoolBorrowFuzzyTest is ERC721FuzzyHelperContract { // check that only deposits above the htp earned interest if (indexes[i] <= _poolUtils.priceToIndex(Maths.wdiv(debt, Maths.wad(tokenIdsToAdd.length)))) { assertGt(deposit, mintAmount_); - assertGt(exchangeRate, 1e27); + assertGt(exchangeRate, 1e18); } else { assertEq(deposit, mintAmount_); - assertEq(exchangeRate, 1e27); + assertEq(exchangeRate, 1e18); } - assertEq(lpAccumulator, mintAmount_ * 1e9); + assertEq(lpAccumulator, mintAmount_); + _assertBucket({ index: indexes[i], - lpBalance: mintAmount_ * 1e9, + lpBalance: mintAmount_, collateral: 0, deposit: deposit, exchangeRate: exchangeRate diff --git a/tests/forge/ERC721Pool/ERC721PoolCollateral.t.sol b/tests/forge/ERC721Pool/ERC721PoolCollateral.t.sol index cee499c25..89c99740a 100644 --- a/tests/forge/ERC721Pool/ERC721PoolCollateral.t.sol +++ b/tests/forge/ERC721Pool/ERC721PoolCollateral.t.sol @@ -3,6 +3,8 @@ pragma solidity 0.8.14; import { ERC721HelperContract } from './ERC721DSTestPlus.sol'; +import { ERC721Pool } from 'src/ERC721Pool.sol'; + import 'src/PoolInfoUtils.sol'; import 'src/libraries/helpers/PoolHelper.sol'; @@ -13,20 +15,14 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { address internal _lender; address internal _lender2; - function setUp() external { + function setUp() virtual external { _borrower = makeAddr("borrower"); _borrower2 = makeAddr("borrower2"); _lender = makeAddr("lender"); _lender2 = makeAddr("lender2"); - // deploy subset pool - uint256[] memory subsetTokenIds = new uint256[](5); - subsetTokenIds[0] = 1; - subsetTokenIds[1] = 3; - subsetTokenIds[2] = 5; - subsetTokenIds[3] = 51; - subsetTokenIds[4] = 53; - _pool = _deploySubsetPool(subsetTokenIds); + // deploy collection pool + _pool = _deployCollectionPool(); _mintAndApproveQuoteTokens(_lender, 200_000 * 1e18); _mintAndApproveQuoteTokens(_borrower, 100 * 1e18); @@ -35,15 +31,11 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { _mintAndApproveCollateralTokens(_borrower2, 53); } - /*******************************/ - /*** ERC721 Collection Tests ***/ - /*******************************/ + /************************************/ + /*** ERC721 Collection Pool Tests ***/ + /************************************/ - /***************************/ - /*** ERC721 Subset Tests ***/ - /***************************/ - - function testPledgeCollateralSubset() external tearDown { + function testPledgeCollateral() external tearDown { // check initial token balances assertEq(_pool.pledgedCollateral(), 0); @@ -55,14 +47,12 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { tokenIdsToAdd[1] = 3; tokenIdsToAdd[2] = 5; - // borrower deposits three NFTs into the subset pool - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - tokenIds: tokenIdsToAdd - } - ); + // borrower deposits three NFTs into the pool + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + tokenIds: tokenIdsToAdd + }); // check token balances after add assertEq(_pool.pledgedCollateral(), Maths.wad(3)); @@ -70,22 +60,7 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { assertEq(_collateral.balanceOf(address(_pool)), 3); } - function testPledgeCollateralNotInSubset() external tearDown { - uint256[] memory tokenIdsToAdd = new uint256[](3); - tokenIdsToAdd[0] = 2; - tokenIdsToAdd[1] = 4; - tokenIdsToAdd[2] = 6; - - // should revert if borrower attempts to add tokens not in the pool subset - _assertPledgeCollateralNotInSubsetRevert( - { - from: _borrower, - tokenIds: tokenIdsToAdd - } - ); - } - - function testPledgeCollateralInSubsetFromDifferentActor() external tearDown { + function testPledgeCollateralFromDifferentActor() external tearDown { // check initial token balances assertEq(_pool.pledgedCollateral(), 0); @@ -93,36 +68,30 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { assertEq(_collateral.balanceOf(_borrower2), 53); assertEq(_collateral.balanceOf(address(_pool)), 0); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 0, - borrowerCollateral: 0, - borrowert0Np: 0, - borrowerCollateralization: 1 * 1e18 - } - ); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 0, - borrowerCollateral: 0, - borrowert0Np: 0, - borrowerCollateralization: 1 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 0, + borrowerCollateral: 0, + borrowert0Np: 0, + borrowerCollateralization: 1 * 1e18 + }); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 0, + borrowerCollateral: 0, + borrowert0Np: 0, + borrowerCollateralization: 1 * 1e18 + }); uint256[] memory tokenIdsToAdd = new uint256[](1); tokenIdsToAdd[0] = 53; - // borrower deposits three NFTs into the subset pool - _pledgeCollateral( - { - from: _borrower2, - borrower: _borrower, - tokenIds: tokenIdsToAdd - } - ); + // borrower deposits three NFTs into the pool + _pledgeCollateral({ + from: _borrower2, + borrower: _borrower, + tokenIds: tokenIdsToAdd + }); // check token balances after add assertEq(_pool.pledgedCollateral(), Maths.wad(1)); @@ -131,24 +100,20 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { assertEq(_collateral.balanceOf(_borrower2), 52); assertEq(_collateral.balanceOf(address(_pool)), 1); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 0, - borrowerCollateral: 1 * 1e18, - borrowert0Np: 0, - borrowerCollateralization: 1 * 1e18 - } - ); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 0, - borrowerCollateral: 0, - borrowert0Np: 0, - borrowerCollateralization: 1 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 0, + borrowerCollateral: 1 * 1e18, + borrowert0Np: 0, + borrowerCollateralization: 1 * 1e18 + }); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 0, + borrowerCollateral: 0, + borrowert0Np: 0, + borrowerCollateralization: 1 * 1e18 + }); } function testPullCollateral() external tearDown { @@ -169,14 +134,12 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { tokenIdsToAdd[1] = 3; tokenIdsToAdd[2] = 5; - // borrower deposits three NFTs into the subset pool - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - tokenIds: tokenIdsToAdd - } - ); + // borrower deposits three NFTs into the pool + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + tokenIds: tokenIdsToAdd + }); // check token balances after add assertEq(_pool.pledgedCollateral(), Maths.wad(3)); @@ -191,25 +154,21 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { assertEq(_collateral.ownerOf(5), address(_pool)); // should fail if trying to pull collateral by an address without pledged collateral - _assertPullInsufficientCollateralRevert( - { - from: _lender, - amount: 3 - } - ); + _assertPullInsufficientCollateralRevert({ + from: _lender, + amount: 3 + }); // borrower2 is owner of NFT assertEq(_collateral.ownerOf(53), _borrower2); tokenIdsToAdd = new uint256[](1); tokenIdsToAdd[0] = 53; - _pledgeCollateral( - { - from: _borrower2, - borrower: _borrower2, - tokenIds: tokenIdsToAdd - } - ); + _pledgeCollateral({ + from: _borrower2, + borrower: _borrower2, + tokenIds: tokenIdsToAdd + }); // check token balances after add assertEq(_pool.pledgedCollateral(), Maths.wad(4)); @@ -242,14 +201,96 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { assertEq(_collateral.ownerOf(3), _borrower); assertEq(_collateral.ownerOf(5), _borrower); - // should fail if borrower tries to pull more NFTs than remaining in pool - _assertPullInsufficientCollateralRevert( - { - from: _borrower, - amount: 3 - } - ); + _assertPullInsufficientCollateralRevert({ + from: _borrower, + amount: 3 + }); + } + + function testPullCollateralToDifferentRecipient() external tearDown { + address tokensReceiver = makeAddr("tokensReceiver"); + + // check initial token balances + assertEq(_pool.pledgedCollateral(), 0); + + assertEq(_collateral.balanceOf(_borrower), 52); + assertEq(_collateral.balanceOf(_borrower2), 53); + assertEq(_collateral.balanceOf(tokensReceiver), 0); + assertEq(_collateral.balanceOf(address(_pool)), 0); + + // borrower is owner of NFTs + assertEq(_collateral.ownerOf(1), _borrower); + assertEq(_collateral.ownerOf(3), _borrower); + assertEq(_collateral.ownerOf(5), _borrower); + + uint256[] memory tokenIdsToAdd = new uint256[](3); + tokenIdsToAdd[0] = 1; + tokenIdsToAdd[1] = 3; + tokenIdsToAdd[2] = 5; + + // borrower deposits three NFTs into the pool + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + tokenIds: tokenIdsToAdd + }); + + // borrower2 deposits three NFTs into the pool + tokenIdsToAdd = new uint256[](1); + tokenIdsToAdd[0] = 53; + _pledgeCollateral({ + from: _borrower2, + borrower: _borrower2, + tokenIds: tokenIdsToAdd + }); + + // check token balances after add + assertEq(_pool.pledgedCollateral(), Maths.wad(4)); + assertEq(_collateral.balanceOf(address(_pool)), 4); + + // pool is owner of pledged NFTs + assertEq(_collateral.ownerOf(1), address(_pool)); + assertEq(_collateral.ownerOf(3), address(_pool)); + assertEq(_collateral.ownerOf(5), address(_pool)); + assertEq(_collateral.ownerOf(53), address(_pool)); + + // borrower removes some of their deposited NFTs from the pool and transfer to a different recipient + changePrank(_borrower); + ERC721Pool(address(_pool)).repayDebt(_borrower, 0, 2, tokensReceiver, MAX_FENWICK_INDEX); + + // check token balances after remove + assertEq(_pool.pledgedCollateral(), Maths.wad(2)); + + assertEq(_collateral.balanceOf(_borrower), 49); + assertEq(_collateral.balanceOf(_borrower2), 52); + assertEq(_collateral.balanceOf(tokensReceiver), 2); + assertEq(_collateral.balanceOf(address(_pool)), 2); + + // pool is owner of remaining pledged NFT + assertEq(_collateral.ownerOf(1), address(_pool)); + // recipient is owner of 2 pulled NFTs + assertEq(_collateral.ownerOf(3), tokensReceiver); + assertEq(_collateral.ownerOf(5), tokensReceiver); + + // borrower2 removes deposited NFT from the pool and transfer to same recipient + changePrank(_borrower2); + ERC721Pool(address(_pool)).repayDebt(_borrower2, 0, 1, tokensReceiver, MAX_FENWICK_INDEX); + + // check token balances after remove + assertEq(_pool.pledgedCollateral(), Maths.wad(1)); + + assertEq(_collateral.balanceOf(_borrower), 49); + assertEq(_collateral.balanceOf(_borrower2), 52); + assertEq(_collateral.balanceOf(tokensReceiver), 3); + assertEq(_collateral.balanceOf(address(_pool)), 1); + + // pool is owner of remaining pledged NFT + assertEq(_collateral.ownerOf(1), address(_pool)); + // recipient is owner of 3 pulled NFTs + assertEq(_collateral.ownerOf(3), tokensReceiver); + assertEq(_collateral.ownerOf(5), tokensReceiver); + assertEq(_collateral.ownerOf(53), tokensReceiver); } function testPullCollateralNotInPool() external tearDown { @@ -263,13 +304,11 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { tokenIdsToAdd[1] = 3; tokenIdsToAdd[2] = 5; - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - tokenIds: tokenIdsToAdd - } - ); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + tokenIds: tokenIdsToAdd + }); // pool is owner of pledged NFTs assertEq(_collateral.ownerOf(1), address(_pool)); @@ -277,12 +316,10 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { assertEq(_collateral.ownerOf(5), address(_pool)); // should revert if borrower attempts to remove more collateral than pledged from pool - _assertPullInsufficientCollateralRevert( - { - from: _borrower, - amount: 5 - } - ); + _assertPullInsufficientCollateralRevert({ + from: _borrower, + amount: 5 + }); // borrower should be able to remove collateral in the pool _repayDebtNoLupCheck({ @@ -299,27 +336,21 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { } function testPullCollateralPartiallyEncumbered() external tearDown { - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2552 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2551 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2550 - } - ); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2552 + }); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2551 + }); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2550 + }); // check initial token balances assertEq(_collateral.balanceOf(_borrower), 52); @@ -352,22 +383,18 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { tokenIdsToAdd[1] = 3; tokenIdsToAdd[2] = 5; - // borrower deposits three NFTs into the subset pool - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - tokenIds: tokenIdsToAdd - } - ); - _borrow( - { - from: _borrower, - amount: 3_000 * 1e18, - indexLimit: 2_551, - newLup: _priceAt(2550) - } - ); + // borrower deposits three NFTs into the pool + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + tokenIds: tokenIdsToAdd + }); + _borrow({ + from: _borrower, + amount: 3_000 * 1e18, + indexLimit: 2_551, + newLup: _priceAt(2550) + }); // check token balances after borrow assertEq(_collateral.balanceOf(_borrower), 49); @@ -434,258 +461,212 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { function testPullCollateralOverlyEncumbered() external tearDown { // lender deposits 10000 Quote into 3 buckets - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2552 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2551 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2550 - } - ); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2552 + }); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2551 + }); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2550 + }); uint256[] memory tokenIdsToAdd = new uint256[](3); tokenIdsToAdd[0] = 1; tokenIdsToAdd[1] = 3; tokenIdsToAdd[2] = 5; - // borrower deposits three NFTs into the subset pool - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - tokenIds: tokenIdsToAdd - } - ); + // borrower deposits three NFTs into the pool + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + tokenIds: tokenIdsToAdd + }); // check collateralization after pledge (uint256 poolDebt,,) = _pool.debtInfo(); assertEq(_encumberance(poolDebt, _lup()), 0); // borrower borrows some quote - _borrow( - { - from: _borrower, - amount: 9_000 * 1e18, - indexLimit: 2_551, - newLup: _priceAt(2550) - } - ); + _borrow({ + from: _borrower, + amount: 9_000 * 1e18, + indexLimit: 2_551, + newLup: _priceAt(2550) + }); // check collateralization after borrow (poolDebt,,) = _pool.debtInfo(); assertEq(_encumberance(poolDebt, _lup()), 2.992021560300836411 * 1e18); // should revert if borrower attempts to pull more collateral than is unencumbered - _assertPullInsufficientCollateralRevert( - { - from: _borrower, - amount: 2 - } - ); + _assertPullInsufficientCollateralRevert({ + from: _borrower, + amount: 2 + }); } function testAddRemoveCollateral() external tearDown { // lender adds some liquidity - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 1692 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 1530 - } - ); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 1692 + }); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 1530 + }); uint256[] memory tokenIds = new uint256[](2); tokenIds[0] = 1; tokenIds[1] = 5; // add three tokens to a single bucket - _addCollateral( - { - from: _borrower, - tokenIds: tokenIds, - index: 1530, - lpAward: 975_232.505322350083963682 * 1e27 - } - ); + _addCollateral({ + from: _borrower, + tokenIds: tokenIds, + index: 1530, + lpAward: 975_232.505322350083963682 * 1e18 + }); // should revert if the actor does not have any LP to remove a token - _assertRemoveCollateralInsufficientLPsRevert( - { - from: _borrower2, - amount: 1, - index: 1530 - } - ); + _assertRemoveCollateralInsufficientLPsRevert({ + from: _borrower2, + amount: 1, + index: 1530 + }); // should revert if we try to remove a token from a bucket with no collateral - _assertRemoveInsufficientCollateralRevert( - { - from: _borrower, - amount: 1, - index: 1692 - } - ); + _assertRemoveInsufficientCollateralRevert({ + from: _borrower, + amount: 1, + index: 1692 + }); // remove one token - _removeCollateral( - { - from: _borrower, - amount: 1, - index: 1530, - lpRedeem: 487_616.252661175041981841 * 1e27 - } - ); + _removeCollateral({ + from: _borrower, + amount: 1, + index: 1530, + lpRedeem: 487_616.252661175041981841 * 1e18 + }); + + _assertBucket({ + index: 1530, + lpBalance: 497_616.252661175041981841 * 1e18, + collateral: Maths.wad(1), + deposit: 10_000 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _borrower, + index: 1530, + lpBalance: 487_616.252661175041981841 * 1e18, + depositTime: _startTime + }); - _assertBucket( - { - index: 1530, - lpBalance: 497_616.252661175041981841 * 1e27, - collateral: Maths.wad(1), - deposit: 10_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _borrower, - index: 1530, - lpBalance: 487_616.252661175041981841 * 1e27, - depositTime: _startTime - } - ); // remove another token - _removeCollateral( - { - from: _borrower, - amount: 1, - index: 1530, - lpRedeem: 487_616.252661175041981841 * 1e27 - } - ); + _removeCollateral({ + from: _borrower, + amount: 1, + index: 1530, + lpRedeem: 487_616.252661175041981841 * 1e18 + }); - _assertBucket( - { - index: 1530, - lpBalance: 10_000 * 1e27, - collateral: 0, - deposit: 10_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _borrower, - index: 1530, - lpBalance: 0, - depositTime: _startTime - } - ); + _assertBucket({ + index: 1530, + lpBalance: 10_000 * 1e18, + collateral: 0, + deposit: 10_000 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _borrower, + index: 1530, + lpBalance: 0, + depositTime: _startTime + }); // lender removes quote token skip(1 days); // skip to avoid penalty - _removeAllLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 1530, - newLup: MAX_PRICE, - lpRedeem: 10_000 * 1e27 - } - ); - _assertBucket( - { - index: 1530, - lpBalance: 0, - collateral: 0, - deposit: 0, - exchangeRate: 1 * 1e27 - } - ); + _removeAllLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 1530, + newLup: MAX_PRICE, + lpRedeem: 10_000 * 1e18 + }); + + _assertBucket({ + index: 1530, + lpBalance: 0, + collateral: 0, + deposit: 0, + exchangeRate: 1 * 1e18 + }); } function testMergeOrRemoveCollateral() external tearDown { for (uint256 i = 3060; i < (3060 + 10); i++) { - _addLiquidity( - { - from: _lender, - amount: 20 * 1e18, - index: i, - newLup: MAX_PRICE, - lpAward: 20 * 1e27 - } - ); + _addLiquidity({ + from: _lender, + amount: 20 * 1e18, + index: i, + newLup: MAX_PRICE, + lpAward: 20 * 1e18 + }); } // borrower pledge collateral and draws debt uint256[] memory tokenIdsToAdd = new uint256[](2); tokenIdsToAdd[0] = 1; tokenIdsToAdd[1] = 3; - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - tokenIds: tokenIdsToAdd - } - ); - _borrow( - { - from: _borrower, - amount: 150 * 1e18, - indexLimit: 8191, - newLup: 228.476350374240318479 * 1e18 - } - ); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + tokenIds: tokenIdsToAdd + }); + _borrow({ + from: _borrower, + amount: 150 * 1e18, + indexLimit: MAX_FENWICK_INDEX, + newLup: 228.476350374240318479 * 1e18 + }); // Borrower starts with possession of tokens 1 and 3 uint256[] memory borrowerTokenIds = new uint256[](2); borrowerTokenIds[0] = 1; borrowerTokenIds[1] = 3; - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 150.144230769230769300 * 1e18, - borrowerCollateral: 2.0 * 1e18, - borrowert0Np: 78.825721153846153882 * 1e18, - borrowerCollateralization: 3.043424968161510485 * 1e18, - tokenIds: borrowerTokenIds - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 150.144230769230769300 * 1e18, + borrowerCollateral: 2.0 * 1e18, + borrowert0Np: 78.825721153846153882 * 1e18, + borrowerCollateralization: 3.043424968161510485 * 1e18, + tokenIds: borrowerTokenIds + }); // skip to render borrower undercollateralized skip(10000 days); - _kick( - { - from: _lender, - borrower: _borrower, - debt: 598.174133241016922933 * 1e18, - collateral: 2.0 * 1e18, - bond: 5.907892673985352325 * 1e18, - transferAmount: 5.907892673985352325 * 1e18 - } - ); + _kick({ + from: _lender, + borrower: _borrower, + debt: 598.174133241016922932 * 1e18, + collateral: 2.0 * 1e18, + bond: 5.907892673985352325 * 1e18, + transferAmount: 5.907892673985352325 * 1e18 + }); skip(32 hours); @@ -724,50 +705,58 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { }) ); + // force an interest accumulation to assert bucket with interest + _addLiquidity({ + from: _lender, + amount: 0 * 1e18, + index: 7000, + newLup: 99836282890, + lpAward: 0 + }); + _assertBucket({ + index: 3060, + lpBalance: 20 * 1e18, + collateral: 0.0000000000000000000 * 1e18, + deposit: 20.010216420146293860 * 1e18, + exchangeRate: 1.000510821007314693 * 1e18 + }); + // Before depositTake: NFTs pledged by liquidated borrower are owned by the borrower in the pool assertEq(_collateral.ownerOf(1), address(_pool)); assertEq(_collateral.ownerOf(3), address(_pool)); // exchange collateral for lpb 3060 - 3070, going down in price for (uint256 i = _i236_59; i < (3060 + 10); i++) { - _depositTake( - { - from: _lender, - borrower: _borrower, - index: i - } - ); + _depositTake({ + from: _lender, + borrower: _borrower, + index: i + }); } - _assertBucket( - { - index: 3060, - lpBalance: 20.2 * 1e27, - collateral: 0.085430491711717314 * 1e18, - deposit: 0, - exchangeRate: 1.000610882095524250072170475 * 1e27 - } - ); + _assertBucket({ + index: 3060, + lpBalance: 20.202020202020202022 * 1e18, + collateral: 0.085430491711717314 * 1e18, + deposit: 0, + exchangeRate: 1.000510821007314698 * 1e18 + }); - _assertBucket( - { - index: 3061, - lpBalance: 20.2 * 1e27, - collateral: 0.085857644170275899 * 1e18, - deposit: 0, - exchangeRate: 1.000610882095524239992886155 * 1e27 - } - ); + _assertBucket({ + index: 3061, + lpBalance: 20.202020202020202019 * 1e18, + collateral: 0.085857644170275899 * 1e18, + deposit: 0, + exchangeRate: 1.000510821007314688 * 1e18 + }); - _assertBucket( - { - index: 3070, - lpBalance: 0, - collateral: 0, - deposit: 0, - exchangeRate: 1 * 1e27 - } - ); + _assertBucket({ + index: 3070, + lpBalance: 0, + collateral: 0, + deposit: 0, + exchangeRate: 1 * 1e18 + }); _assertAuction( AuctionParams({ @@ -795,7 +784,7 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { encumberedCollateral: 4407944209.541175956055268556 * 1e18, poolDebt: 440.072765067090279852 * 1e18, actualUtilization: 0, - targetUtilization: 3_123_578_486.651416548727612650 * 1e18, + targetUtilization: 2_996_091_127.870826153174895975 * 1e18, minDebtAmount: 0, loans: 0, maxBorrower: address(0), @@ -808,51 +797,52 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { borrowerTokenIds = new uint256[](1); borrowerTokenIds[0] = 1; - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 440.072765067090279852 * 1e18, - borrowerCollateral: 1.126214674710621229 * 1e18, - borrowert0Np: 78.825721153846153882 * 1e18, - borrowerCollateralization: 0.000000000255496581 * 1e18, - tokenIds: borrowerTokenIds - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 440.072765067090279852 * 1e18, + borrowerCollateral: 1.126214674710621229 * 1e18, + borrowert0Np: 78.825721153846153882 * 1e18, + borrowerCollateralization: 0.000000000255496581 * 1e18, + tokenIds: borrowerTokenIds + }); // after depositTake but before take: NFTs pledged by liquidated borrower are owned by the pool assertEq(_collateral.ownerOf(1), address(_pool)); assertEq(_collateral.ownerOf(3), address(_pool)); - _take( - { - from: _lender, - borrower: _borrower, - maxCollateral: 2.0 * 1e18, - bondChange: 0.000000052051493471 * 1e18, - givenAmount: 0.000005205149347131 * 1e18, - collateralTaken: 1.126214674710621229 * 1e18, - isReward: true - } - ); + _take({ + from: _lender, + borrower: _borrower, + maxCollateral: 2 * 1e18, + bondChange: 0.000000046218092021 * 1e18, + givenAmount: 0.000004621809202112 * 1e18, + collateralTaken: 1 * 1e18, + isReward: true + }); // Borrower has < 1 collateral, no NFTs in possession borrowerTokenIds = new uint256[](0); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 440.072759913992426192 * 1e18, - borrowerCollateral: 0.126214674710621229 * 1e18, - borrowert0Np: 78.825721153846153882 * 1e18, - borrowerCollateralization: 0.000000000028633456 * 1e18, - tokenIds: borrowerTokenIds - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 440.072760491499169762 * 1e18, + borrowerCollateral: 0.126214674710621229 * 1e18, + borrowert0Np: 78.825721153846153882 * 1e18, + borrowerCollateralization: 0.000000000028633456 * 1e18, + tokenIds: borrowerTokenIds + }); // after take: NFT with ID 1, pledged by liquidated borrower is owned by the taker assertEq(_collateral.ownerOf(1), address(_pool)); assertEq(_collateral.ownerOf(3), _lender); + // subsequent take should fail as there's less than 1 NFT remaining in the loan + _assertTakeInsufficentCollateralRevert({ + from: _lender, + borrower: _borrower2, + maxCollateral: 1 * 1e18 + }); + // 70.16 hours skip(4210 minutes); @@ -861,37 +851,33 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { borrower: _borrower, active: true, kicker: address(_lender), - bondSize: 5.907892726036845796 * 1e18, + bondSize: 5.907892720203444346 * 1e18, bondFactor: 0.010 * 1e18, kickTime: block.timestamp - (32 hours + 4210 minutes), kickMomp: 0.000000099836282890 * 1e18, - totalBondEscrowed: 5.907892726036845796 * 1e18, + totalBondEscrowed: 5.907892720203444346 * 1e18, auctionPrice: 0 * 1e18, - debtInAuction: 440.072759913992426192 * 1e18, - thresholdPrice: 3_488.390484128255500242 * 1e18, + debtInAuction: 440.072760491499169762 * 1e18, + thresholdPrice: 3_488.390488706064472517 * 1e18, neutralPrice: 310.164365384230997074 * 1e18 }) ); - _settle( - { - from: _lender, - borrower: _borrower, - maxDepth: 10, - settledDebt: 111.818402566884385900 * 1e18 - } - ); + _settle({ + from: _lender, + borrower: _borrower, + maxDepth: 10, + settledDebt: 111.818402713623487748 * 1e18 + }); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 0, - borrowerCollateral: 0, - borrowert0Np: 78.825721153846153882 * 1e18, - borrowerCollateralization: 1.0 * 1e18, - tokenIds: borrowerTokenIds - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 0, + borrowerCollateral: 0, + borrowert0Np: 78.825721153846153882 * 1e18, + borrowerCollateralization: 1.0 * 1e18, + tokenIds: borrowerTokenIds + }); // after take: NFT, 1 pledged by liquidated borrower is owned by the taker assertEq(_collateral.ownerOf(1), address(_pool)); @@ -914,49 +900,40 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { }) ); - _assertBucket( - { - index: 3060, - lpBalance: 20.2 * 1e27, - collateral: 0.085430491711717314 * 1e18, - deposit: 0, - exchangeRate: 1.000610882095524250072170475 * 1e27 - } - ); - _assertBucket( - { - index: 3069, - lpBalance: 20.2 * 1e27, - collateral: 0.089352655062849951 * 1e18, - deposit: 0, - exchangeRate: 1.000610882095524241676916623 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 3069, - lpBalance: 20.2 * 1e27, - depositTime: _startTime + 10000 days + 32 hours - } - ); - _assertBucket( - { - index: 7388, - lpBalance: 0.000000012600803969278909906 * 1e27, // LPs awarded to borrower for settled collateral - collateral: 0.126214674710621229 * 1e18, // settled collateral amount - deposit: 0, - exchangeRate: 1.000000000000000000006624324 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _borrower, - index: 7388, - lpBalance: 0.000000012600803969278909906 * 1e27, - depositTime: _startTime + 10000 days + 32 hours + 4210 minutes - } - ); + _assertBucket({ + index: 3060, + lpBalance: 20.202020202020202022 * 1e18, + collateral: 0.085430491711717314 * 1e18, + deposit: 0, + exchangeRate: 1.000510821007314698 * 1e18 + }); + _assertBucket({ + index: 3069, + lpBalance: 20.202020202020202020 * 1e18, + collateral: 0.089352655062849951 * 1e18, + deposit: 0, + exchangeRate: 1.000510821007314689 * 1e18 + }); + _assertLenderLpBalance({ + lender: _lender, + index: 3069, + lpBalance: 20.202020202020202020 * 1e18, + depositTime: _startTime + 10000 days + 32 hours + }); + _assertBucket({ + index: 7388, + lpBalance: 0.000000012600803969 * 1e18, // LPs awarded to borrower for settled collateral + collateral: 0.126214674710621229 * 1e18, // settled collateral amount + deposit: 0, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _borrower, + index: 7388, + lpBalance: 0.000000012600803969 * 1e18, + depositTime: _startTime + 10000 days + 32 hours + 4210 minutes + }); + assertEq(_collateral.balanceOf(_lender), 1); assertEq(_collateral.balanceOf(_borrower), 50); assertEq(_collateral.balanceOf(address(_pool)), 1); @@ -968,183 +945,149 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { removalIndexes[removalI] = i; removalI++; } + // Reverts because 3059 is a higher price than 3060, must merge down in price - _assertCannotMergeToHigherPriceRevert( - { - from: _lender, - toIndex: 3059, - noOfNFTsToRemove: 1.0, - removeCollateralAtIndex: removalIndexes - } - ); + _assertCannotMergeToHigherPriceRevert({ + from: _lender, + toIndex: 3059, + noOfNFTsToRemove: 1.0, + removeCollateralAtIndex: removalIndexes + }); - _mergeOrRemoveCollateral( - { - from: _lender, - toIndex: 3069, - noOfNFTsToRemove: 1.0, - collateralMerged: 0.873785325289378771 * 1e18, - removeCollateralAtIndex: removalIndexes, - toIndexLps: 197.657763058028917103677822434 * 1e27 - } - ); + _mergeOrRemoveCollateral({ + from: _lender, + toIndex: 3070, + noOfNFTsToRemove: 1.0, + collateralMerged: 0.873785325289378771 * 1e18, + removeCollateralAtIndex: removalIndexes, + toIndexLps: 196.674391102516337019 * 1e18 + }); - _assertBucket( - { - index: 3060, - lpBalance: 0, - collateral: 0, - deposit: 0, - exchangeRate: 1.0 * 1e27 - } - ); - _assertBucket( - { - index: 3061, - lpBalance: 0, - collateral: 0, - deposit: 0, - exchangeRate: 1.0 * 1e27 - } - ); - _assertBucket( - { - index: 3069, - lpBalance: 197.657763058028917103677822434 * 1e27, // new LPs amount accounting collateral merged in bucket - collateral: 0.873785325289378771 * 1e18, // reflects collateral merged in the bucket - deposit: 0, - exchangeRate: 0.999999999999999999999999999 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 3069, - lpBalance: 197.657763058028917103677822434 * 1e27, - depositTime: _startTime + 10000 days + 32 hours + 4210 minutes - } - ); - _assertBucket( - { - index: 7388, - lpBalance: 0.000000012600803969278909906 * 1e27, // LPs awarded to borrower for settled collateral - collateral: 0.126214674710621229 * 1e18, // settled collateral amount - deposit: 0, - exchangeRate: 1.000000000000000000006624324 * 1e27 - } - ); + _assertBucket({ + index: 3060, + lpBalance: 0, + collateral: 0, + deposit: 0, + exchangeRate: 1 * 1e18 + }); + _assertBucket({ + index: 3061, + lpBalance: 0, + collateral: 0, + deposit: 0, + exchangeRate: 1 * 1e18 + }); + _assertBucket({ + index: 3070, + lpBalance: 196.674391102516337019 * 1e18, // new LPs amount accounting collateral merged in bucket + collateral: 0.873785325289378771 * 1e18, // reflects collateral merged in the bucket + deposit: 0, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _lender, + index: 3070, + lpBalance: 196.674391102516337019 * 1e18, + depositTime: _startTime + 10000 days + 32 hours + 4210 minutes + }); + _assertBucket({ + index: 7388, + lpBalance: 0.000000012600803969 * 1e18, // LPs awarded to borrower for settled collateral + collateral: 0.126214674710621229 * 1e18, // settled collateral amount + deposit: 0, + exchangeRate: 1 * 1e18 + }); assertEq(_collateral.balanceOf(_lender), 1); assertEq(_collateral.balanceOf(_borrower), 50); assertEq(_collateral.balanceOf(address(_pool)), 1); // lender deposit quote tokens in bucket 7388 in order to claim and merge settled collateral and to be able to remove entire NFT - _addLiquidity( - { - from: _lender, - amount: 10 * 1e18, - index: 7388, - lpAward: 9.999999999999999999933756760 * 1e27, // LPs awarded to lender for depositing quote tokens in bucket 7388 - newLup: MAX_PRICE - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 7388, - lpBalance: 9.999999999999999999933756760 * 1e27, // lender now owns LPs in bucket 7388 which can be used to merge bucket collateral - depositTime: _startTime + 10000 days + (32 hours + 4210 minutes) - } - ); + _addLiquidity({ + from: _lender, + amount: 10 * 1e18, + index: 7388, + lpAward: 10 * 1e18, // LPs awarded to lender for depositing quote tokens in bucket 7388 + newLup: MAX_PRICE + }); + + _assertLenderLpBalance({ + lender: _lender, + index: 7388, + lpBalance: 10 * 1e18, // lender now owns LPs in bucket 7388 which can be used to merge bucket collateral + depositTime: _startTime + 10000 days + (32 hours + 4210 minutes) + }); // collateral is now splitted accross buckets 3069 and 7388 uint256[] memory allRemovalIndexes = new uint256[](2); - allRemovalIndexes[0] = 3069; + allRemovalIndexes[0] = 3070; allRemovalIndexes[1] = 7388; - _mergeOrRemoveCollateral( - { - from: _lender, - toIndex: 7388, - noOfNFTsToRemove: 1, - collateralMerged: 1 * 1e18, - removeCollateralAtIndex: allRemovalIndexes, - toIndexLps: 0 - } - ); + _mergeOrRemoveCollateral({ + from: _lender, + toIndex: 7388, + noOfNFTsToRemove: 1, + collateralMerged: 1 * 1e18, + removeCollateralAtIndex: allRemovalIndexes, + toIndexLps: 0 + }); - _assertBucket( - { - index: 3060, - lpBalance: 0, - collateral: 0, - deposit: 0, - exchangeRate: 1.0 * 1e27 - } - ); - _assertBucket( - { - index: 3061, - lpBalance: 0, - collateral: 0, - deposit: 0, - exchangeRate: 1.0 * 1e27 - } - ); - _assertBucket( - { - index: 3069, - lpBalance: 0, - collateral: 0, - deposit: 0, - exchangeRate: 1.0 * 1e27 - } - ); - _assertBucket( - { - index: 7388, - lpBalance: 10.000000000000000000212666669 * 1e27, // LPs in bucket 7388 diminished when NFT merged and removed - collateral: 0, // no collateral remaining as it was merged and removed - deposit: 10 * 1e18, - exchangeRate: 0.999999999999999999978733333 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _lender, - index: 7388, - lpBalance: 9.999999987399196030933756763 * 1e27, // lender LPs decreased with the amount used to merge NFT - depositTime: _startTime + 10000 days + (32 hours + 4210 minutes) - } - ); - _assertLenderLpBalance( - { - lender: _borrower, - index: 7388, - lpBalance: 0.000000012600803969278909906 * 1e27, // Borrower LPs remain the same in the bucket - depositTime: _startTime + 10000 days + (32 hours + 4210 minutes) - } - ); + _assertBucket({ + index: 3060, + lpBalance: 0, + collateral: 0, + deposit: 0, + exchangeRate: 1.0 * 1e18 + }); + _assertBucket({ + index: 3061, + lpBalance: 0, + collateral: 0, + deposit: 0, + exchangeRate: 1.0 * 1e18 + }); + _assertBucket({ + index: 3070, + lpBalance: 0, + collateral: 0, + deposit: 0, + exchangeRate: 1.0 * 1e18 + }); + _assertBucket({ + index: 7388, + lpBalance: 10 * 1e18, // LPs in bucket 7388 diminished when NFT merged and removed + collateral: 0, // no collateral remaining as it was merged and removed + deposit: 10 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _lender, + index: 7388, + lpBalance: 9.999999987399196031 * 1e18, // lender LPs decreased with the amount used to merge NFT + depositTime: _startTime + 10000 days + (32 hours + 4210 minutes) + }); + _assertLenderLpBalance({ + lender: _borrower, + index: 7388, + lpBalance: 0.000000012600803969 * 1e18, // Borrower LPs remain the same in the bucket + depositTime: _startTime + 10000 days + (32 hours + 4210 minutes) + }); - _removeAllLiquidity( - { - from: _lender, - amount: 9.987201910492245717 * 1e18, - index: 7388, - newLup: MAX_PRICE, - lpRedeem: 9.999999987399196030933756763 * 1e27 - } - ); + _removeAllLiquidity({ + from: _lender, + amount: 9.987201910492245717 * 1e18, + index: 7388, + newLup: MAX_PRICE, + lpRedeem: 9.999999987399196031 * 1e18 + }); - _assertBucket( - { - index: 7388, - lpBalance: 0.000000012600803969278909906 * 1e27, // LPs in bucket 7388 diminished when NFT merged and removed - collateral: 0, // no collateral remaining as it was merged and removed - deposit: 0.000000012600803969 * 1e18, - exchangeRate: 0.999999999977865705499427682 * 1e27 - } - ); + _assertBucket({ + index: 7388, + lpBalance: 0.000000012600803969 * 1e18, // LPs in bucket 7388 diminished when NFT merged and removed + collateral: 0, // no collateral remaining as it was merged and removed + deposit: 0.000000012600803969 * 1e18, + exchangeRate: 1 * 1e18 + }); _assertPool( PoolParams({ @@ -1155,7 +1098,7 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { encumberedCollateral: 0, poolDebt: 0, actualUtilization: 0, - targetUtilization: 3123578486.651416548727612650 * 1e18, + targetUtilization: 2_996_091_127.870826153174895975 * 1e18, minDebtAmount: 0, loans: 0, maxBorrower: address(0), @@ -1167,6 +1110,61 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { assertEq(_collateral.balanceOf(_lender), 2); assertEq(_collateral.balanceOf(_borrower), 50); assertEq(_collateral.balanceOf(address(_pool)), 0); + } +} +contract ERC721SubsetPoolCollateralTest is ERC721PoolCollateralTest { + + function setUp() override external { + _borrower = makeAddr("borrower"); + _borrower2 = makeAddr("borrower2"); + _lender = makeAddr("lender"); + _lender2 = makeAddr("lender2"); + + // deploy subset pool + uint256[] memory subsetTokenIds = new uint256[](5); + subsetTokenIds[0] = 1; + subsetTokenIds[1] = 3; + subsetTokenIds[2] = 5; + subsetTokenIds[3] = 51; + subsetTokenIds[4] = 53; + _pool = _deploySubsetPool(subsetTokenIds); + + _mintAndApproveQuoteTokens(_lender, 200_000 * 1e18); + _mintAndApproveQuoteTokens(_borrower, 100 * 1e18); + + _mintAndApproveCollateralTokens(_borrower, 52); + _mintAndApproveCollateralTokens(_borrower2, 53); + } + + /********************************/ + /*** ERC721 Subset Pool Tests ***/ + /********************************/ + + function testPledgeCollateralNotInSubset() external tearDown { + uint256[] memory tokenIdsToAdd = new uint256[](3); + tokenIdsToAdd[0] = 2; + tokenIdsToAdd[1] = 4; + tokenIdsToAdd[2] = 6; + + // should revert if borrower attempts to add tokens not in the pool subset + _assertPledgeCollateralNotInSubsetRevert({ + from: _borrower, + tokenIds: tokenIdsToAdd + }); + } + + function testRemoveCollateralReverts() external tearDown { + uint256 testIndex = 6248; + uint256[] memory tokenIdsToAdd = new uint256[](2); + tokenIdsToAdd[0] = 3; + tokenIdsToAdd[1] = 5; + + _assertAddCollateralExpiredRevert({ + from: _lender, + tokenIds: tokenIdsToAdd, + index: testIndex, + expiry: block.timestamp - 15 + }); } } diff --git a/tests/forge/ERC721Pool/ERC721PoolFactory.t.sol b/tests/forge/ERC721Pool/ERC721PoolFactory.t.sol index 19539ff7e..b8b5cc45e 100644 --- a/tests/forge/ERC721Pool/ERC721PoolFactory.t.sol +++ b/tests/forge/ERC721Pool/ERC721PoolFactory.t.sol @@ -2,12 +2,13 @@ pragma solidity 0.8.14; import { ERC721HelperContract } from './ERC721DSTestPlus.sol'; -import { NFTCollateralToken, Token } from '../utils/Tokens.sol'; +import { NFTCollateralToken, TokenWithNDecimals } from '../utils/Tokens.sol'; import { ERC721Pool } from 'src/ERC721Pool.sol'; import { ERC721PoolFactory } from 'src/ERC721PoolFactory.sol'; import { IPoolErrors } from 'src/interfaces/pool/commons/IPoolErrors.sol'; import { IPoolFactory } from 'src/interfaces/pool/IPoolFactory.sol'; +import { IERC721PoolFactory } from 'src/interfaces/pool/erc721/IERC721PoolFactory.sol'; contract ERC721PoolFactoryTest is ERC721HelperContract { address internal _NFTCollectionPoolAddress; @@ -23,7 +24,7 @@ contract ERC721PoolFactoryTest is ERC721HelperContract { function setUp() external { _startTime = block.timestamp; _collateral = new NFTCollateralToken(); - _quote = new Token("Quote", "Q"); + _quote = new TokenWithNDecimals("Quote", "Q", 18); // deploy factory _factory = new ERC721PoolFactory(_ajna); @@ -89,24 +90,20 @@ contract ERC721PoolFactoryTest is ERC721HelperContract { function testDeployERC721CollectionPoolWithZeroAddress() external { // should revert if trying to deploy with zero address as collateral - _assertDeployWith0xAddressRevert( - { - poolFactory: address(_factory), - collateral: address(0), - quote: address(_quote), - interestRate: 0.05 * 10**18 - } - ); + _assertDeployWith0xAddressRevert({ + poolFactory: address(_factory), + collateral: address(0), + quote: address(_quote), + interestRate: 0.05 * 10**18 + }); // should revert if trying to deploy with zero address as quote token - _assertDeployWith0xAddressRevert( - { - poolFactory: address(_factory), - collateral: address(_collateral), - quote: address(0), - interestRate: 0.05 * 10**18 - } - ); + _assertDeployWith0xAddressRevert({ + poolFactory: address(_factory), + collateral: address(_collateral), + quote: address(0), + interestRate: 0.05 * 10**18 + }); // check tracking of deployed pools assertEq(_factory.getDeployedPoolsList().length, 3); @@ -115,52 +112,73 @@ contract ERC721PoolFactoryTest is ERC721HelperContract { function testDeployERC721CollectionPoolWithInvalidRate() external { // should revert if trying to deploy with interest rate lower than accepted - _assertDeployWithInvalidRateRevert( - { - poolFactory: address(_factory), - collateral: address(_quote), - quote: address(_quote), - interestRate: 10**18 - } - ); + _assertDeployWithInvalidRateRevert({ + poolFactory: address(_factory), + collateral: address(_quote), + quote: address(_quote), + interestRate: 10**18 + }); // should revert if trying to deploy with interest rate higher than accepted - _assertDeployWithInvalidRateRevert( - { - poolFactory: address(_factory), - collateral: address(_quote), - quote: address(_quote), - interestRate: 2 * 10**18 - } - ); + _assertDeployWithInvalidRateRevert({ + poolFactory: address(_factory), + collateral: address(_quote), + quote: address(_quote), + interestRate: 2 * 10**18 + }); // check tracking of deployed pools assertEq(_factory.getDeployedPoolsList().length, 3); assertEq(_factory.getNumberOfDeployedPools(), 3); } + // FIXME: This test is exceeding block gas limit function testDeployERC721CollectionPoolWithNonNFTAddress() external { // should revert if trying to deploy with non NFT - _assertDeployWithNonNFTRevert( - { - poolFactory: address(_factory), - collateral: address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2), - quote: address(_quote), - interestRate: 0.05 * 10**18 - } - ); + _assertDeployWithNonNFTRevert({ + poolFactory: address(_factory), + collateral: address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2), + quote: address(_quote), + interestRate: 0.05 * 10**18 + }); } function testDeployERC721PoolMultipleTimes() external { // should revert if trying to deploy same pool one more time - _assertDeployMultipleTimesRevert( - { - poolFactory: address(_factory), - collateral: address(_collateral), - quote: address(_quote), - interestRate: 0.05 * 10**18 - } + _assertDeployMultipleTimesRevert({ + poolFactory: address(_factory), + collateral: address(_collateral), + quote: address(_quote), + interestRate: 0.05 * 10**18 + }); + } + + function testDeployERC721PoolWithMinRate() external { + uint256[] memory tokenIds = new uint256[](0); + _factory.deployPool( + address(new NFTCollateralToken()), + address(new TokenWithNDecimals("Quote", "Q1", 18)), + tokenIds, + 0.01 * 10**18 + ); + + // check tracking of deployed pools + assertEq(_factory.getDeployedPoolsList().length, 4); + assertEq(_factory.getNumberOfDeployedPools(), 4); + } + + function testDeployERC721PoolWithMaxRate() external { + uint256[] memory tokenIds = new uint256[](0); + _factory.deployPool( + address(new NFTCollateralToken()), + address(new TokenWithNDecimals("Quote", "Q1", 18)), + tokenIds, + 0.1 * 10**18 ); + + // check tracking of deployed pools + assertEq(_factory.getDeployedPoolsList().length, 4); + assertEq(_factory.getNumberOfDeployedPools(), 4); } function testDeployERC721CollectionPool() external { @@ -188,6 +206,32 @@ contract ERC721PoolFactoryTest is ERC721HelperContract { /*** ERC721 Collection Subset Tests ***/ /**************************************/ + function testGetNFTSubsetHashTokenOrdering() external { + uint256[] memory tokenIdsOne = new uint256[](3); + tokenIdsOne[0] = 1; + tokenIdsOne[1] = 70; + tokenIdsOne[2] = 3; + uint256[] memory tokenIdsTwo = new uint256[](3); + tokenIdsTwo[0] = 1; + tokenIdsTwo[1] = 2; + tokenIdsTwo[2] = 3; + uint256[] memory tokenIdsThree = new uint256[](3); + tokenIdsThree[0] = 1; + tokenIdsThree[1] = 2; + tokenIdsThree[2] = 2; + + // check sort order + vm.expectRevert(IERC721PoolFactory.TokenIdSubsetInvalid.selector); + _factory.getNFTSubsetHash(tokenIdsOne); + + // check valid subset hash + assertEq(_factory.getNFTSubsetHash(tokenIdsTwo), keccak256(abi.encode(tokenIdsTwo))); + + // check for duplicate tokenIds + vm.expectRevert(IERC721PoolFactory.TokenIdSubsetInvalid.selector); + _factory.getNFTSubsetHash(tokenIdsThree); + } + function testDeployERC721SubsetPoolWithZeroAddress() external { uint256[] memory tokenIdsTestSubset = new uint256[](3); tokenIdsTestSubset[0] = 1; diff --git a/tests/forge/ERC721Pool/ERC721FlashloanQuote.t.sol b/tests/forge/ERC721Pool/ERC721PoolFlashloan.t.sol similarity index 89% rename from tests/forge/ERC721Pool/ERC721FlashloanQuote.t.sol rename to tests/forge/ERC721Pool/ERC721PoolFlashloan.t.sol index 0838f8b44..bb0e01d3e 100644 --- a/tests/forge/ERC721Pool/ERC721FlashloanQuote.t.sol +++ b/tests/forge/ERC721Pool/ERC721PoolFlashloan.t.sol @@ -31,32 +31,28 @@ contract ERC721PoolFlashloanTest is ERC721HelperContract { _bucketPrice = 251.186576139566121965 * 1e18; _bucketId = _indexOf(_bucketPrice); assertEq(_bucketId, 3048); - _addInitialLiquidity( - { - from: _lender, - amount: 300 * 1e18, - index: _bucketId - } - ); + + _addInitialLiquidity({ + from: _lender, + amount: 300 * 1e18, + index: _bucketId + }); // borrower draws debt uint256[] memory tokenIdsToAdd = new uint256[](1); tokenIdsToAdd[0] = 1; - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - tokenIds: tokenIdsToAdd - } - ); - _borrow( - { - from: _borrower, - amount: 200 * 1e18, - indexLimit: _bucketId, - newLup: _bucketPrice - } - ); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + tokenIds: tokenIdsToAdd + }); + _borrow({ + from: _borrower, + amount: 200 * 1e18, + indexLimit: _bucketId, + newLup: _bucketPrice + }); + (uint256 poolDebt,,) = _pool.debtInfo(); assertEq(poolDebt, 200.192307692307692400 * 1e18); } diff --git a/tests/forge/ERC721Pool/ERC721PoolInterest.t.sol b/tests/forge/ERC721Pool/ERC721PoolInterest.t.sol index 806ebb9ee..d1a2df150 100644 --- a/tests/forge/ERC721Pool/ERC721PoolInterest.t.sol +++ b/tests/forge/ERC721Pool/ERC721PoolInterest.t.sol @@ -47,41 +47,32 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { } function testBorrowerInterestCalculation() external tearDown { - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2550 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2551 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2552 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2553 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2554 - } - ); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2550 + }); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2551 + }); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2552 + }); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2553 + }); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2554 + }); + (uint256 liquidityAdded, , , , ) = _poolUtils.poolLoansInfo(address(_pool)); skip(10 days); @@ -91,59 +82,54 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { tokenIdsToAdd[0] = 1; tokenIdsToAdd[1] = 3; tokenIdsToAdd[2] = 5; - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - tokenIds: tokenIdsToAdd - } - ); - _borrow( - { - from: _borrower, - amount: 5_000 * 1e18, - indexLimit: 2_551, - newLup: 3_010.892022197881557845 * 1e18 - } - ); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + tokenIds: tokenIdsToAdd + }); + _borrow({ + from: _borrower, + amount: 5_000 * 1e18, + indexLimit: 2_551, + newLup: 3_010.892022197881557845 * 1e18 + }); + uint256 expectedDebt = 5_004.326923076923075000 * 1e18; (uint256 poolDebt,,) = _pool.debtInfo(); assertEq(poolDebt, expectedDebt); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: expectedDebt, - borrowerCollateral: 3 * 1e18, - borrowert0Np: 1_743.173878205128204457 * 1e18, - borrowerCollateralization: 1.804973217265326249 * 1e18 - } - ); + + _assertBorrower({ + borrower: _borrower, + borrowerDebt: expectedDebt, + borrowerCollateral: 3 * 1e18, + borrowert0Np: 1_743.173878205128204457 * 1e18, + borrowerCollateralization: 1.804973217265326249 * 1e18 + }); + _assertLenderInterest(liquidityAdded, 0); // borrower pledge additional collateral after some time has passed skip(10 days); tokenIdsToAdd = new uint256[](1); tokenIdsToAdd[0] = 51; - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - tokenIds: tokenIdsToAdd - } - ); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + tokenIds: tokenIdsToAdd + }); expectedDebt = 5_010.500446015624727374 * 1e18; (poolDebt,,) = _pool.debtInfo(); assertEq(poolDebt, expectedDebt); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: expectedDebt, - borrowerCollateral: 4 * 1e18, - borrowert0Np: 1_743.173878205128204457 * 1e18, - borrowerCollateralization: 2.403665705362551645 * 1e18 - } - ); + + _assertBorrower({ + borrower: _borrower, + borrowerDebt: expectedDebt, + borrowerCollateral: 4 * 1e18, + borrowert0Np: 1_743.173878205128204457 * 1e18, + borrowerCollateralization: 2.403665705362551645 * 1e18 + }); + _assertLenderInterest(liquidityAdded, 5.279480966289700000 * 1e18); // borrower pulls some of their collateral after some time has passed @@ -160,41 +146,39 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { expectedDebt = 5_016.063127975675193806 * 1e18; (poolDebt,,) = _pool.debtInfo(); assertEq(poolDebt, expectedDebt); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: expectedDebt, - borrowerCollateral: 3 * 1e18, - borrowert0Np: 1_735.667387820512819845 * 1e18, - borrowerCollateralization: 1.800750077529217167 * 1e18 - } - ); + + _assertBorrower({ + borrower: _borrower, + borrowerDebt: expectedDebt, + borrowerCollateral: 3 * 1e18, + borrowert0Np: 1_735.667387820512819845 * 1e18, + borrowerCollateralization: 1.800750077529217167 * 1e18 + }); + _assertLenderInterest(liquidityAdded, 10.036615877540250000 * 1e18); // borrower borrows some additional quote after some time has passed skip(10 days); - _borrow( - { - from: _borrower, - amount: 1_000 * 1e18, - indexLimit: 3_000, - newLup: 3_010.892022197881557845 * 1e18 - } - ); + _borrow({ + from: _borrower, + amount: 1_000 * 1e18, + indexLimit: 3_000, + newLup: 3_010.892022197881557845 * 1e18 + }); expectedDebt = 6_021.775783320497493092 * 1e18; (poolDebt,,) = _pool.debtInfo(); assertEq(poolDebt, expectedDebt); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: expectedDebt, - borrowerCollateral: 3 * 1e18, - borrowert0Np: 2_073.483875782488916529 * 1e18, - borrowerCollateralization: 1.500002057800446964 * 1e18 - } - ); + + _assertBorrower({ + borrower: _borrower, + borrowerDebt: expectedDebt, + borrowerCollateral: 3 * 1e18, + borrowert0Np: 2_073.483875782488916529 * 1e18, + borrowerCollateralization: 1.500002057800446964 * 1e18 + }); + _assertLenderInterest(liquidityAdded, 14.322580066674600000 * 1e18); // mint additional quote to borrower to enable repayment @@ -202,6 +186,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { // borrower repays their loan after some additional time skip(10 days); + _approveAndRepayDebt({ from: _borrower, borrower: _borrower, @@ -214,41 +199,33 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { (poolDebt,,) = _pool.debtInfo(); assertEq(poolDebt, 0); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 0, - borrowerCollateral: 3 * 1e18, - borrowert0Np: 2_073.483875782488916529 * 1e18, - borrowerCollateralization: 1 * 1e18 - } - ); - + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 0, + borrowerCollateral: 3 * 1e18, + borrowert0Np: 2_073.483875782488916529 * 1e18, + borrowerCollateralization: 1 * 1e18 + }); } function testMultipleBorrowerInterestAccumulation() external tearDown { // lender deposits 10000 Quote into 3 buckets - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2550 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2551 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2552 - } - ); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2550 + }); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2551 + }); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2552 + }); + (uint256 liquidityAdded, , , , ) = _poolUtils.poolLoansInfo(address(_pool)); skip(2 hours); @@ -258,81 +235,73 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { tokenIdsToAdd[0] = 1; tokenIdsToAdd[1] = 3; tokenIdsToAdd[2] = 5; - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - tokenIds: tokenIdsToAdd - } - ); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + tokenIds: tokenIdsToAdd + }); uint256 borrowAmount = 8_000 * 1e18; - _borrow( - { - from: _borrower, - amount: borrowAmount, - indexLimit: 2_551, - newLup: _priceAt(2550) - } - ); + + _borrow({ + from: _borrower, + amount: borrowAmount, + indexLimit: 2_551, + newLup: _priceAt(2550) + }); + uint256 expectedBorrower1Debt = 8_007.692307692307696000 * 1e18; (uint256 poolDebt,,) = _pool.debtInfo(); assertEq(poolDebt, expectedBorrower1Debt); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: expectedBorrower1Debt, - borrowerCollateral: 3 * 1e18, - borrowert0Np: 2_802.692307692307693600 * 1e18, - borrowerCollateralization: 1.127999893042434013 * 1e18 - } - ); + + _assertBorrower({ + borrower: _borrower, + borrowerDebt: expectedBorrower1Debt, + borrowerCollateral: 3 * 1e18, + borrowert0Np: 2_802.692307692307693600 * 1e18, + borrowerCollateralization: 1.127999893042434013 * 1e18 + }); skip(4 hours); // borrower 2 pledges one NFT and takes out a loan with TP around 2750 tokenIdsToAdd = new uint256[](1); tokenIdsToAdd[0] = 53; - _pledgeCollateral( - { - from: _borrower2, - borrower: _borrower2, - tokenIds: tokenIdsToAdd - } - ); + _pledgeCollateral({ + from: _borrower2, + borrower: _borrower2, + tokenIds: tokenIdsToAdd + }); borrowAmount = 2_750 * 1e18; - _borrow( - { - from: _borrower2, - amount: borrowAmount, - indexLimit: 3_000, - newLup: _priceAt(2551) - } - ); + _borrow({ + from: _borrower2, + amount: borrowAmount, + indexLimit: 3_000, + newLup: _priceAt(2551) + }); + expectedBorrower1Debt = 8_007.875133804645608008 * 1e18; uint256 expectedBorrower2Debt = 2_752.644230769230770500 * 1e18; uint256 expectedPoolDebt = 10_760.519364573876378508 * 1e18; (poolDebt,,) = _pool.debtInfo(); assertEq(poolDebt, expectedPoolDebt); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: expectedBorrower1Debt, - borrowerCollateral: 3 * 1e18, - borrowert0Np: 2_802.692307692307693600 * 1e18, - borrowerCollateralization: 1.122362328272840838 * 1e18 - } - ); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: expectedBorrower2Debt, - borrowerCollateral: 1 * 1e18, - borrowert0Np: 2_904.661507289418461411 * 1e18, - borrowerCollateralization: 1.088376197116173336 * 1e18 - } - ); + + _assertBorrower({ + borrower: _borrower, + borrowerDebt: expectedBorrower1Debt, + borrowerCollateral: 3 * 1e18, + borrowert0Np: 2_802.692307692307693600 * 1e18, + borrowerCollateralization: 1.122362328272840838 * 1e18 + }); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: expectedBorrower2Debt, + borrowerCollateral: 1 * 1e18, + borrowert0Np: 2_904.661507289418461411 * 1e18, + borrowerCollateralization: 1.088376197116173336 * 1e18 + }); + _assertLenderInterest(liquidityAdded, 0.158098662307440000 * 1e18); skip(4 hours); @@ -340,148 +309,133 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { // borrower 3 pledges one NFT and takes out a loan with TP around 2500 tokenIdsToAdd = new uint256[](1); tokenIdsToAdd[0] = 73; - _pledgeCollateral( - { - from: _borrower3, - borrower: _borrower3, - tokenIds: tokenIdsToAdd - } - ); + _pledgeCollateral({ + from: _borrower3, + borrower: _borrower3, + tokenIds: tokenIdsToAdd + }); + borrowAmount = 2_500 * 1e18; - _borrow( - { - from: _borrower3, - amount: borrowAmount, - indexLimit: 3_000, - newLup: _priceAt(2551) - } - ); + _borrow({ + from: _borrower3, + amount: borrowAmount, + indexLimit: 3_000, + newLup: _priceAt(2551) + }); + expectedBorrower1Debt = 8_008.057964091143327677 * 1e18; expectedBorrower2Debt = 2_752.707077245346929053 * 1e18; - uint256 expectedBorrower3Debt = 2_502.403846153846155000 * 1e18; - expectedPoolDebt = 13_263.168887490336411731 * 1e18; + uint256 expectedBorrower3Debt = 2_502.403846153846154999 * 1e18; + expectedPoolDebt = 13_263.168887490336411730 * 1e18; + (poolDebt,,) = _pool.debtInfo(); assertEq(poolDebt, expectedPoolDebt); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: expectedBorrower1Debt, - borrowerCollateral: 3 * 1e18, - borrowert0Np: 2_802.692307692307693600 * 1e18, - borrowerCollateralization: 1.122336703854666979 * 1e18 - } - ); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: expectedBorrower2Debt, - borrowerCollateral: 1 * 1e18, - borrowert0Np: 2_904.661507289418461411 * 1e18, - borrowerCollateralization: 1.088351348628209297 * 1e18 - } - ); - _assertBorrower( - { - borrower: _borrower3, - borrowerDebt: expectedBorrower3Debt, - borrowerCollateral: 1 * 1e18, - borrowert0Np: 2_640.541083248800813687 * 1e18, - borrowerCollateralization: 1.197213816827790670 * 1e18 - } - ); + + _assertBorrower({ + borrower: _borrower, + borrowerDebt: expectedBorrower1Debt, + borrowerCollateral: 3 * 1e18, + borrowert0Np: 2_802.692307692307693600 * 1e18, + borrowerCollateralization: 1.122336703854666979 * 1e18 + }); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: expectedBorrower2Debt, + borrowerCollateral: 1 * 1e18, + borrowert0Np: 2_904.661507289418461411 * 1e18, + borrowerCollateralization: 1.088351348628209297 * 1e18 + }); + _assertBorrower({ + borrower: _borrower3, + borrowerDebt: expectedBorrower3Debt, + borrowerCollateral: 1 * 1e18, + borrowert0Np: 2_640.541083248800813686 * 1e18, + borrowerCollateralization: 1.197213816827790670 * 1e18 + }); + _assertLenderInterest(liquidityAdded, 0.371995968618930000 * 1e18); skip(4 hours); // trigger an interest accumulation - _addLiquidity( - { - from: _lender, - amount: 1 * 1e18, - index: 2550, - lpAward: 0.999978753161905147653029533 * 1e27, - newLup: 2995.912459898389633881 * 1e18 - } - ); + _addLiquidity({ + from: _lender, + amount: 1 * 1e18, + index: 2550, + lpAward: 0.999978753161905148 * 1e18, + newLup: 2995.912459898389633881 * 1e18 + }); liquidityAdded += 1e18; // check pool and borrower debt to confirm interest has accumulated - expectedPoolDebt = 13_263.471703022178416340 * 1e18; + expectedPoolDebt = 13_263.471703022178416339 * 1e18; + (poolDebt,,) = _pool.debtInfo(); assertEq(poolDebt, expectedPoolDebt); + _assertLenderInterest(liquidityAdded, 0.637418685977190000 * 1e18); expectedBorrower1Debt = 8_008.240798551896146546 * 1e18; - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: expectedBorrower1Debt, - borrowerCollateral: 3 * 1e18, - borrowert0Np: 2_802.692307692307693600 * 1e18, - borrowerCollateralization: 1.122311080021518821 * 1e18 - } - ); + + _assertBorrower({ + borrower: _borrower, + borrowerDebt: expectedBorrower1Debt, + borrowerCollateral: 3 * 1e18, + borrowert0Np: 2_802.692307692307693600 * 1e18, + borrowerCollateralization: 1.122311080021518821 * 1e18 + }); + expectedBorrower2Debt = 2_752.769925156330518052 * 1e18; - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: expectedBorrower2Debt, - borrowerCollateral: 1 * 1e18, - borrowert0Np: 2_904.661507289418461411 * 1e18, - borrowerCollateralization: 1.088326500707555859 * 1e18 - } - ); - expectedBorrower3Debt = 2_502.460979313951751742 * 1e18; - _assertBorrower( - { - borrower: _borrower3, - borrowerDebt: expectedBorrower3Debt, - borrowerCollateral: 1 * 1e18, - borrowert0Np: 2_640.541083248800813687 * 1e18, - borrowerCollateralization: 1.197186483491030227 * 1e18 - } - ); + + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: expectedBorrower2Debt, + borrowerCollateral: 1 * 1e18, + borrowert0Np: 2_904.661507289418461411 * 1e18, + borrowerCollateralization: 1.088326500707555859 * 1e18 + }); + + expectedBorrower3Debt = 2_502.460979313951751741 * 1e18; + + _assertBorrower({ + borrower: _borrower3, + borrowerDebt: expectedBorrower3Debt, + borrowerCollateral: 1 * 1e18, + borrowert0Np: 2_640.541083248800813686 * 1e18, + borrowerCollateralization: 1.197186483491030227 * 1e18 + }); // ensure debt from the three borrowers adds up to the pool debt assertEq(expectedPoolDebt, expectedBorrower1Debt + expectedBorrower2Debt + expectedBorrower3Debt); } function testBorrowerInterestCalculationAfterRepayingAllDebtOnce() external tearDown { - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2550 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2551 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2552 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2553 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2554 - } - ); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2550 + }); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2551 + }); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2552 + }); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2553 + }); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2554 + }); + (uint256 liquidityAdded, , , , ) = _poolUtils.poolLoansInfo(address(_pool)); skip(10 days); @@ -491,34 +445,29 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { tokenIdsToAdd[0] = 1; tokenIdsToAdd[1] = 3; tokenIdsToAdd[2] = 5; - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - tokenIds: tokenIdsToAdd - } - ); - _borrow( - { - from: _borrower, - amount: 5_000 * 1e18, - indexLimit: 2_551, - newLup: 3_010.892022197881557845 * 1e18 - } - ); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + tokenIds: tokenIdsToAdd + }); + _borrow({ + from: _borrower, + amount: 5_000 * 1e18, + indexLimit: 2_551, + newLup: 3_010.892022197881557845 * 1e18 + }); uint256 expectedDebt = 5_004.326923076923075000 * 1e18; (uint256 poolDebt, , ) = _pool.debtInfo(); assertEq(poolDebt, expectedDebt); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: expectedDebt, - borrowerCollateral: 3 * 1e18, - borrowert0Np: 1_743.173878205128204457 * 1e18, - borrowerCollateralization: 1.804973217265326249 * 1e18 - } - ); + + _assertBorrower({ + borrower: _borrower, + borrowerDebt: expectedDebt, + borrowerCollateral: 3 * 1e18, + borrowert0Np: 1_743.173878205128204457 * 1e18, + borrowerCollateralization: 1.804973217265326249 * 1e18 + }); // time passes and interest accrues skip(30 days); @@ -526,15 +475,14 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { expectedDebt = 5_022.870348947539432923 * 1e18; (poolDebt, , ) = _pool.debtInfo(); assertEq(poolDebt, expectedDebt); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: expectedDebt, - borrowerCollateral: 3 * 1e18, - borrowert0Np: 1_743.173878205128204457 * 1e18, - borrowerCollateralization: 1.798309619615464420 * 1e18 - } - ); + + _assertBorrower({ + borrower: _borrower, + borrowerDebt: expectedDebt, + borrowerCollateral: 3 * 1e18, + borrowert0Np: 1_743.173878205128204457 * 1e18, + borrowerCollateralization: 1.798309619615464420 * 1e18 + }); // mint additional quote to borrower to enable repayment deal(address(_quote), _borrower, 20_000 * 1e18); @@ -554,41 +502,37 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { (poolDebt, , ) = _pool.debtInfo(); assertEq(poolDebt, 0); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 0, - borrowerCollateral: 3 * 1e18, - borrowert0Np: 1_743.173878205128204457 * 1e18, - borrowerCollateralization: 1 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 0, + borrowerCollateral: 3 * 1e18, + borrowert0Np: 1_743.173878205128204457 * 1e18, + borrowerCollateralization: 1 * 1e18 + }); + _assertLenderInterest(liquidityAdded, 21.157033792511550000 * 1e18); // borrower borrows again once repayed all debt - _borrow( - { - from: _borrower, - amount: 5_000 * 1e18, - indexLimit: 2_551, - newLup: 3_010.892022197881557845 * 1e18 - } - ); - - expectedDebt = 5_003.894230769230770000 * 1e18; + _borrow({ + from: _borrower, + amount: 5_000 * 1e18, + indexLimit: 2_551, + newLup: 3_010.892022197881557845 * 1e18 + }); + + expectedDebt = 5_003.894230769230769999 * 1e18; (poolDebt, , ) = _pool.debtInfo(); assertEq(poolDebt, expectedDebt); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: expectedDebt, - borrowerCollateral: 3 * 1e18, - borrowert0Np: 1_726.979669209494250313 * 1e18, - borrowerCollateralization: 1.805129295309881815 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: expectedDebt, + borrowerCollateral: 3 * 1e18, + borrowert0Np: 1_726.979669209494250312 * 1e18, + borrowerCollateralization: 1.805129295309881815 * 1e18 + }); + _assertLenderInterest(liquidityAdded, 21.157033792511550000 * 1e18); @@ -596,26 +540,24 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { skip(10 days); tokenIdsToAdd = new uint256[](1); tokenIdsToAdd[0] = 51; - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - tokenIds: tokenIdsToAdd - } - ); - - expectedDebt = 5_009.449578476990224066 * 1e18; + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + tokenIds: tokenIdsToAdd + }); + + expectedDebt = 5_009.449578476990224065 * 1e18; (poolDebt, , ) = _pool.debtInfo(); assertEq(poolDebt, expectedDebt); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: expectedDebt, - borrowerCollateral: 4 * 1e18, - borrowert0Np: 1_726.979669209494250313 * 1e18, - borrowerCollateralization: 2.404169939255701731 * 1e18 - } - ); + + _assertBorrower({ + borrower: _borrower, + borrowerDebt: expectedDebt, + borrowerCollateral: 4 * 1e18, + borrowert0Np: 1_726.979669209494250312 * 1e18, + borrowerCollateralization: 2.404169939255701731 * 1e18 + }); + _assertLenderInterest(liquidityAdded, 25.907847709272800000 * 1e18); // borrower pulls some of their collateral after some time has passed @@ -629,44 +571,42 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { collateralToPull: 1 }); - expectedDebt = 5_014.454664494689841710 * 1e18; + expectedDebt = 5_014.454664494689841709 * 1e18; (poolDebt, , ) = _pool.debtInfo(); assertEq(poolDebt, expectedDebt); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: expectedDebt, - borrowerCollateral: 3 * 1e18, - borrowert0Np: 1_720.257643586910442803 * 1e18, - borrowerCollateralization: 1.801327695821111558 * 1e18 - } - ); + + _assertBorrower({ + borrower: _borrower, + borrowerDebt: expectedDebt, + borrowerCollateral: 3 * 1e18, + borrowert0Np: 1_720.257643586910442802 * 1e18, + borrowerCollateralization: 1.801327695821111558 * 1e18 + }); + _assertLenderInterest(liquidityAdded, 30.188116923725550000 * 1e18); // borrower borrows some additional quote after some time has passed skip(10 days); - _borrow( - { - from: _borrower, - amount: 1_000 * 1e18, - indexLimit: 3_000, - newLup: 3_010.892022197881557845 * 1e18 - } - ); + _borrow({ + from: _borrower, + amount: 1_000 * 1e18, + indexLimit: 3_000, + newLup: 3_010.892022197881557845 * 1e18 + }); - expectedDebt = 6_019.594382773827921758 * 1e18; + expectedDebt = 6_019.594382773827921756 * 1e18; (poolDebt, , ) = _pool.debtInfo(); assertEq(poolDebt, expectedDebt); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: expectedDebt, - borrowerCollateral: 3 * 1e18, - borrowert0Np: 2_055.969470907112040316 * 1e18, - borrowerCollateralization: 1.500545633513497515 * 1e18 - } - ); + + _assertBorrower({ + borrower: _borrower, + borrowerDebt: expectedDebt, + borrowerCollateral: 3 * 1e18, + borrowert0Np: 2_055.969470907112040315 * 1e18, + borrowerCollateralization: 1.500545633513497515 * 1e18 + }); + _assertLenderInterest(liquidityAdded, 34.044037663170400000 * 1e18); // mint additional quote to borrower to enable repayment @@ -679,7 +619,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { from: _borrower, borrower: _borrower, amountToRepay: 7_000 * 1e18, - amountRepaid: 6_024.465544800440916672 * 1e18, + amountRepaid: 6_024.465544800440916669 * 1e18, collateralToPull: 0, newLup: MAX_PRICE }); @@ -687,15 +627,14 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { (poolDebt, , ) = _pool.debtInfo(); assertEq(poolDebt, 0); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 0, - borrowerCollateral: 3 * 1e18, - borrowert0Np: 2_055.969470907112040316 * 1e18, - borrowerCollateralization: 1 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 0, + borrowerCollateral: 3 * 1e18, + borrowert0Np: 2_055.969470907112040315 * 1e18, + borrowerCollateralization: 1 * 1e18 + }); + _assertLenderInterest(liquidityAdded, 38.215088437572850000 * 1e18); } } \ No newline at end of file diff --git a/tests/forge/ERC721Pool/ERC721PoolLiquidationsDepositTake.t.sol b/tests/forge/ERC721Pool/ERC721PoolLiquidationsDepositTake.t.sol new file mode 100644 index 000000000..341bdb1d9 --- /dev/null +++ b/tests/forge/ERC721Pool/ERC721PoolLiquidationsDepositTake.t.sol @@ -0,0 +1,308 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.14; + +import { ERC721HelperContract } from './ERC721DSTestPlus.sol'; + +import 'src/libraries/helpers/PoolHelper.sol'; + +contract ERC721PoolLiquidationsDepositTakeTest is ERC721HelperContract { + + address internal _borrower; + address internal _borrower2; + address internal _lender; + address internal _lender2; + address internal _taker; + + function setUp() external { + _borrower = makeAddr("borrower"); + _borrower2 = makeAddr("borrower2"); + _lender = makeAddr("lender"); + _lender2 = makeAddr("lender2"); + _taker = makeAddr("taker"); + + // deploy subset pool + uint256[] memory subsetTokenIds = new uint256[](6); + subsetTokenIds[0] = 1; + subsetTokenIds[1] = 3; + subsetTokenIds[2] = 5; + subsetTokenIds[3] = 51; + subsetTokenIds[4] = 53; + subsetTokenIds[5] = 73; + _pool = _deploySubsetPool(subsetTokenIds); + + _mintAndApproveQuoteTokens(_lender, 120_000 * 1e18); + _mintAndApproveQuoteTokens(_lender2, 120_000 * 1e18); + _mintAndApproveQuoteTokens(_borrower, 10 * 1e18); + + _mintAndApproveCollateralTokens(_borrower, 6); + _mintAndApproveCollateralTokens(_borrower2, 74); + + // Lender adds Quote token accross 5 prices + _addInitialLiquidity({ + from: _lender, + amount: 2_000 * 1e18, + index: _i9_91 + }); + _addInitialLiquidity({ + from: _lender, + amount: 5_000 * 1e18, + index: _i9_81 + }); + _addInitialLiquidity({ + from: _lender, + amount: 11_000 * 1e18, + index: _i9_72 + }); + _addInitialLiquidity({ + from: _lender, + amount: 25_000 * 1e18, + index: _i9_62 + }); + _addInitialLiquidity({ + from: _lender, + amount: 30_000 * 1e18, + index: _i9_52 + }); + + // first borrower adds collateral token and borrows + uint256[] memory tokenIdsToAdd = new uint256[](2); + tokenIdsToAdd[0] = 1; + tokenIdsToAdd[1] = 3; + + uint256 expectedNewLup = 9.917184843435912074 * 1e18; + + // borrower deposits two NFTs into the subset pool and borrows + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + tokenIds: tokenIdsToAdd + }); + _borrow({ + from: _borrower, + amount: 19.8 * 1e18, + indexLimit: _i9_91, + newLup: expectedNewLup + }); + + // second borrower deposits three NFTs into the subset pool and borrows + tokenIdsToAdd = new uint256[](3); + tokenIdsToAdd[0] = 51; + tokenIdsToAdd[1] = 53; + tokenIdsToAdd[2] = 73; + _pledgeCollateral({ + from: _borrower2, + borrower: _borrower2, + tokenIds: tokenIdsToAdd + }); + _borrow({ + from: _borrower2, + amount: 15 * 1e18, + indexLimit: _i9_72, + newLup: expectedNewLup + }); + + /*****************************/ + /*** Assert pre-kick state ***/ + /*****************************/ + + _assertPool( + PoolParams({ + htp: 9.909519230769230774 * 1e18, + lup: expectedNewLup, + poolSize: 73_000 * 1e18, + pledgedCollateral: 5 * 1e18, + encumberedCollateral: 3.512434434608473285 * 1e18, + poolDebt: 34.833461538461538478 * 1e18, + actualUtilization: 0.000477170706006322 * 1e18, + targetUtilization: 1 * 1e18, + minDebtAmount: 1.741673076923076924 * 1e18, + loans: 2, + maxBorrower: address(_borrower), + interestRate: 0.05 * 1e18, + interestRateUpdate: _startTime + }) + ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 19.819038461538461548 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.404995192307692312 * 1e18, + borrowerCollateralization: 1.000773560501591181 * 1e18 + }); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 15.014423076923076930 * 1e18, + borrowerCollateral: 3 * 1e18, + borrowert0Np: 5.255048076923076925 * 1e18, + borrowerCollateralization: 1.981531649793150539 * 1e18 + }); + + assertEq(_quote.balanceOf(_lender), 47_000 * 1e18); + + // Skip to make borrower undercollateralized + skip(1000 days); + + _kick({ + from: _lender, + borrower: _borrower, + debt: 23.012828827714740289 * 1e18, + collateral: 2 * 1e18, + bond: 0.227287198298417188 * 1e18, + transferAmount: 0.227287198298417188 * 1e18 + }); + + /******************************/ + /*** Assert Post-kick state ***/ + /******************************/ + + _assertPool( + PoolParams({ + htp: 6.582216822103492762 * 1e18, + lup: 9.917184843435912074 * 1e18, + poolSize: 73_000 * 1e18, + pledgedCollateral: 5 * 1e18, + encumberedCollateral: 4.056751649452525709 * 1e18, + poolDebt: 40.231555971534224231 * 1e18, + actualUtilization: 0.000551117205089510 * 1e18, + targetUtilization: 0.811350329890505142 * 1e18, + minDebtAmount: 4.023155597153422423 * 1e18, + loans: 1, + maxBorrower: address(_borrower2), + interestRate: 0.045 * 1e18, + interestRateUpdate: block.timestamp + }) + ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 23.012828827714740289 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.404995192307692312 * 1e18, + borrowerCollateralization: 0.861883162446546169 * 1e18 + }); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 17.218727143819483942 * 1e18, + borrowerCollateral: 3 * 1e18, + borrowert0Np: 5.255048076923076925 * 1e18, + borrowerCollateralization: 1.727860269914713433 * 1e18 + }); + } + + function testDepositTakeNFTAndSettleAuction() external { + + skip(5 hours); + + _assertAuction( + AuctionParams({ + borrower: _borrower, + active: true, + kicker: _lender, + bondSize: 0.227287198298417188 * 1e18, + bondFactor: 0.01 * 1e18, + kickTime: block.timestamp - 5 hours, + kickMomp: 9.917184843435912074 * 1e18, + totalBondEscrowed: 0.227287198298417188 * 1e18, + auctionPrice: 23.865155821333804736 * 1e18, + debtInAuction: 23.012828827714740289 * 1e18, + thresholdPrice: 11.506709959118993144 * 1e18, + neutralPrice: 11.932577910666902372 * 1e18 + }) + ); + + _addLiquidity({ + from: _lender, + amount: 15.0 * 1e18, + index: _i1505_26, + lpAward: 15.0 * 1e18, + newLup: 9.917184843435912074 * 1e18 + }); + + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 23.013419918237986289 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.404995192307692312 * 1e18, + borrowerCollateralization: 0.861861025320848319 * 1e18 + }); + + // before deposit take: NFTs pledged by auctioned borrower are owned by the pool + assertEq(_collateral.ownerOf(3), address(_pool)); + assertEq(_collateral.ownerOf(1), address(_pool)); + + _depositTake({ + from: _taker, + borrower: _borrower, + kicker: _lender, + index: _i1505_26, + collateralArbed: 0.009965031187761219 * 1e18, + quoteTokenAmount: 14.999999999999999995 * 1e18, + bondChange: 0.15 * 1e18, + isReward: false, + lpAwardTaker: 0, + lpAwardKicker: 0 + }); + + _assertAuction( + AuctionParams({ + borrower: _borrower, + active: false, + kicker: address(0), + bondSize: 0, + bondFactor: 0, + kickTime: 0, + kickMomp: 0, + totalBondEscrowed: 0, + auctionPrice: 0, + debtInAuction: 0, + thresholdPrice: 9.624359312514645329 * 1e18, + neutralPrice: 0 + }) + ); + // borrower is compensated LPs for fractional collateral + _assertLenderLpBalance({ + lender: _borrower, + index: 3519, + lpBalance: 23.737330323739529015 * 1e18, + depositTime: block.timestamp + }); + _assertBucket({ + index: _i1505_26, + lpBalance: 15 * 1e18, + collateral: 0.009965031187761219 * 1e18, + deposit: 0, + exchangeRate: 1 * 1e18 + }); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 9.624359312514645329 * 1e18, + borrowerCollateral: 1 * 1e18, + borrowert0Np: 8.769696613728507377 * 1e18, + borrowerCollateralization: 1.030425457052554443 * 1e18 + }); + _assertLenderLpBalance({ + lender: _taker, + index: _i1505_26, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: _lender, + index: _i1505_26, + lpBalance: 15.0 * 1e18, + depositTime: block.timestamp + }); + + // borrower should be able to repay and pull collateral from the pool + _repayDebtNoLupCheck({ + from: _borrower, + borrower: _borrower, + amountToRepay: 10 * 1e18, + amountRepaid: 10 * 1e18, + collateralToPull: 1 + }); + + // after deposit take and pull: NFT taken remains in pool, the pulled one goes to borrower + assertEq(_collateral.ownerOf(3), address(_pool)); + assertEq(_collateral.ownerOf(1), _borrower); + } +} diff --git a/tests/forge/ERC721Pool/ERC721PoolLiquidationsKick.t.sol b/tests/forge/ERC721Pool/ERC721PoolLiquidationsKick.t.sol index c56487125..956703212 100644 --- a/tests/forge/ERC721Pool/ERC721PoolLiquidationsKick.t.sol +++ b/tests/forge/ERC721Pool/ERC721PoolLiquidationsKick.t.sol @@ -34,84 +34,65 @@ contract ERC721PoolLiquidationsKickTest is ERC721HelperContract { _mintAndApproveCollateralTokens(_borrower2, 74); // Lender adds Quote token accross 5 prices - _addInitialLiquidity( - { - from: _lender, - amount: 2_000 * 1e18, - index: _i9_91 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 5_000 * 1e18, - index: _i9_81 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 11_000 * 1e18, - index: _i9_72 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 25_000 * 1e18, - index: _i9_62 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 30_000 * 1e18, - index: _i9_52 - } - ); + _addInitialLiquidity({ + from: _lender, + amount: 2_000 * 1e18, + index: _i9_91 + }); + _addInitialLiquidity({ + from: _lender, + amount: 5_000 * 1e18, + index: _i9_81 + }); + _addInitialLiquidity({ + from: _lender, + amount: 11_000 * 1e18, + index: _i9_72 + }); + _addInitialLiquidity({ + from: _lender, + amount: 25_000 * 1e18, + index: _i9_62 + }); + _addInitialLiquidity({ + from: _lender, + amount: 30_000 * 1e18, + index: _i9_52 + }); // first borrower adds collateral token and borrows uint256[] memory tokenIdsToAdd = new uint256[](2); tokenIdsToAdd[0] = 1; tokenIdsToAdd[1] = 3; - // borrower deposits two NFTs into the subset pool and borrows - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - tokenIds: tokenIdsToAdd - } - ); - _borrow( - { - from: _borrower, - amount: 19.8 * 1e18, - indexLimit: _i9_91, - newLup: 9.917184843435912074 * 1e18 - } - ); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + tokenIds: tokenIdsToAdd + }); + _borrow({ + from: _borrower, + amount: 19.8 * 1e18, + indexLimit: _i9_91, + newLup: 9.917184843435912074 * 1e18 + }); // second borrower deposits three NFTs into the subset pool and borrows tokenIdsToAdd = new uint256[](3); tokenIdsToAdd[0] = 51; tokenIdsToAdd[1] = 53; tokenIdsToAdd[2] = 73; - _pledgeCollateral( - { - from: _borrower2, - borrower: _borrower2, - tokenIds: tokenIdsToAdd - } - ); - _borrow( - { - from: _borrower2, - amount: 15 * 1e18, - indexLimit: _i9_72, - newLup: 9.917184843435912074 * 1e18 - } - ); + _pledgeCollateral({ + from: _borrower2, + borrower: _borrower2, + tokenIds: tokenIdsToAdd + }); + _borrow({ + from: _borrower2, + amount: 15 * 1e18, + indexLimit: _i9_72, + newLup: 9.917184843435912074 * 1e18 + }); /*****************************/ /*** Assert pre-kick state ***/ @@ -134,24 +115,21 @@ contract ERC721PoolLiquidationsKickTest is ERC721HelperContract { interestRateUpdate: _startTime }) ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 19.819038461538461548 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.404995192307692312 * 1e18, - borrowerCollateralization: 1.000773560501591181 * 1e18 - } - ); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 15.014423076923076930 * 1e18, - borrowerCollateral: 3 * 1e18, - borrowert0Np: 5.255048076923076925 * 1e18, - borrowerCollateralization: 1.981531649793150539 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 19.819038461538461548 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.404995192307692312 * 1e18, + borrowerCollateralization: 1.000773560501591181 * 1e18 + }); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 15.014423076923076930 * 1e18, + borrowerCollateral: 3 * 1e18, + borrowert0Np: 5.255048076923076925 * 1e18, + borrowerCollateralization: 1.981531649793150539 * 1e18 + }); + assertEq(_quote.balanceOf(_lender), 47_000 * 1e18); } @@ -176,27 +154,22 @@ contract ERC721PoolLiquidationsKickTest is ERC721HelperContract { neutralPrice: 0 }) ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 22.728719829841718804 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.404995192307692312 * 1e18, + borrowerCollateralization: 0.872656701977127996 * 1e18 + }); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 22.728719829841718804 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.404995192307692312 * 1e18, - borrowerCollateralization: 0.872656701977127996 * 1e18 - } - ); - - _kick( - { - from: _lender, - borrower: _borrower, - debt: 23.012828827714740289 * 1e18, - collateral: 2 * 1e18, - bond: 0.227287198298417188 * 1e18, - transferAmount: 0.227287198298417188 * 1e18 - } - ); + _kick({ + from: _lender, + borrower: _borrower, + debt: 23.012828827714740289 * 1e18, + collateral: 2 * 1e18, + bond: 0.227287198298417188 * 1e18, + transferAmount: 0.227287198298417188 * 1e18 + }); /******************************/ /*** Assert Post-kick state ***/ @@ -219,25 +192,6 @@ contract ERC721PoolLiquidationsKickTest is ERC721HelperContract { interestRateUpdate: block.timestamp }) ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 23.012828827714740289 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.404995192307692312 * 1e18, - borrowerCollateralization: 0.861883162446546169 * 1e18 - } - ); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 17.218727143819483942 * 1e18, - borrowerCollateral: 3 * 1e18, - borrowert0Np: 5.255048076923076925 * 1e18, - borrowerCollateralization: 1.727860269914713433 * 1e18 - } - ); - assertEq(_quote.balanceOf(_lender), 46_999.772712801701582812 * 1e18); _assertAuction( AuctionParams({ borrower: _borrower, @@ -254,46 +208,53 @@ contract ERC721PoolLiquidationsKickTest is ERC721HelperContract { neutralPrice: 11.932577910666902372 * 1e18 }) ); - _assertKicker( - { - kicker: _lender, - claimable: 0, - locked: 0.227287198298417188 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 23.012828827714740289 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.404995192307692312 * 1e18, + borrowerCollateralization: 0.861883162446546169 * 1e18 + }); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 17.218727143819483942 * 1e18, + borrowerCollateral: 3 * 1e18, + borrowert0Np: 5.255048076923076925 * 1e18, + borrowerCollateralization: 1.727860269914713433 * 1e18 + }); + _assertKicker({ + kicker: _lender, + claimable: 0, + locked: 0.227287198298417188 * 1e18 + }); + + assertEq(_quote.balanceOf(_lender), 46_999.772712801701582812 * 1e18); // kick should fail if borrower in liquidation - _assertKickAuctionActiveRevert( - { - from: _lender, - borrower: _borrower - } - ); + _assertKickAuctionActiveRevert({ + from: _lender, + borrower: _borrower + }); // kick should fail if borrower properly collateralized - _assertKickCollateralizedBorrowerRevert( - { - from: _lender, - borrower: _borrower2 - } - ); + _assertKickCollateralizedBorrowerRevert({ + from: _lender, + borrower: _borrower2 + }); // check locked pool actions if auction kicked for more than 72 hours and auction head not cleared skip(80 hours); - _assertRemoveLiquidityAuctionNotClearedRevert( - { - from: _lender, - amount: 1_000 * 1e18, - index: _i9_91 - } - ); - _assertRemoveCollateralAuctionNotClearedRevert( - { - from: _lender, - amount: 10 * 1e18, - index: _i9_91 - } - ); + _assertRemoveLiquidityAuctionNotClearedRevert({ + from: _lender, + amount: 1_000 * 1e18, + index: _i9_91 + }); + + _assertRemoveCollateralAuctionNotClearedRevert({ + from: _lender, + amount: 10 * 1e18, + index: _i9_91 + }); } function testKickSubsetPoolAndSettleByRepayAndPledge() external tearDown { @@ -316,26 +277,23 @@ contract ERC721PoolLiquidationsKickTest is ERC721HelperContract { neutralPrice: 0 }) ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 22.728719829841718804 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.404995192307692312 * 1e18, - borrowerCollateralization: 0.872656701977127996 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 22.728719829841718804 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.404995192307692312 * 1e18, + borrowerCollateralization: 0.872656701977127996 * 1e18 + }); + + _kick({ + from: _lender, + borrower: _borrower, + debt: 23.012828827714740289 * 1e18, + collateral: 2 * 1e18, + bond: 0.227287198298417188 * 1e18, + transferAmount: 0.227287198298417188 * 1e18 + }); - _kick( - { - from: _lender, - borrower: _borrower, - debt: 23.012828827714740289 * 1e18, - collateral: 2 * 1e18, - bond: 0.227287198298417188 * 1e18, - transferAmount: 0.227287198298417188 * 1e18 - } - ); _assertAuction( AuctionParams({ borrower: _borrower, @@ -352,17 +310,16 @@ contract ERC721PoolLiquidationsKickTest is ERC721HelperContract { neutralPrice: 11.932577910666902372 * 1e18 }) ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 23.012828827714740289 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.404995192307692312 * 1e18, - borrowerCollateralization: 0.861883162446546169 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 23.012828827714740289 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.404995192307692312 * 1e18, + borrowerCollateralization: 0.861883162446546169 * 1e18 + }); uint256 snapshot = vm.snapshot(); + // borrower repays debt in order to exit from auction _repayDebt({ from: _borrower, @@ -389,27 +346,24 @@ contract ERC721PoolLiquidationsKickTest is ERC721HelperContract { neutralPrice: 0 }) ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 0, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 0, - borrowerCollateralization: 1 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 0, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 0, + borrowerCollateralization: 1 * 1e18 + }); + vm.revertTo(snapshot); // borrower pledge one more NFT to exit from auction uint256[] memory tokenIdsToAdd = new uint256[](1); tokenIdsToAdd[0] = 5; - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - tokenIds: tokenIdsToAdd - } - ); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + tokenIds: tokenIdsToAdd + }); _assertAuction( AuctionParams({ @@ -427,15 +381,13 @@ contract ERC721PoolLiquidationsKickTest is ERC721HelperContract { neutralPrice: 0 }) ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 23.012828827714740289 * 1e18, - borrowerCollateral: 3 * 1e18, - borrowert0Np: 6.989927127403846156 * 1e18, - borrowerCollateralization: 1.292824743669819254 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 23.012828827714740289 * 1e18, + borrowerCollateral: 3 * 1e18, + borrowert0Np: 6.989927127403846156 * 1e18, + borrowerCollateralization: 1.292824743669819254 * 1e18 + }); } } \ No newline at end of file diff --git a/tests/forge/ERC721Pool/ERC721PoolLiquidationsSettle.t.sol b/tests/forge/ERC721Pool/ERC721PoolLiquidationsSettle.t.sol index 48f3bf1c9..18010fd62 100644 --- a/tests/forge/ERC721Pool/ERC721PoolLiquidationsSettle.t.sol +++ b/tests/forge/ERC721Pool/ERC721PoolLiquidationsSettle.t.sol @@ -34,36 +34,29 @@ contract ERC721PoolLiquidationsSettleTest is ERC721HelperContract { _mintAndApproveCollateralTokens(_borrower2, 74); // Lender adds Quote token in one bucket - _addInitialLiquidity( - { - from: _lender, - amount: 15_000 * 1e18, - index: 2500 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 1_000 * 1e18, - index: 2501 - } - ); + _addInitialLiquidity({ + from: _lender, + amount: 15_000 * 1e18, + index: 2500 + }); + _addInitialLiquidity({ + from: _lender, + amount: 1_000 * 1e18, + index: 2501 + }); // first borrower adds collateral token and borrows uint256[] memory tokenIdsToAdd = new uint256[](2); tokenIdsToAdd[0] = 1; tokenIdsToAdd[1] = 3; - // borrower deposits two NFTs into the subset pool and borrows - _drawDebtNoLupCheck( - { - from: _borrower, - borrower: _borrower, - amountToBorrow: 5_000 * 1e18, - limitIndex: 5000, - tokenIds: tokenIdsToAdd - } - ); + _drawDebtNoLupCheck({ + from: _borrower, + borrower: _borrower, + amountToBorrow: 5_000 * 1e18, + limitIndex: 5000, + tokenIds: tokenIdsToAdd + }); // second borrower deposits three NFTs into the subset pool and borrows tokenIdsToAdd = new uint256[](3); @@ -71,15 +64,13 @@ contract ERC721PoolLiquidationsSettleTest is ERC721HelperContract { tokenIdsToAdd[1] = 53; tokenIdsToAdd[2] = 73; // borrower deposits two NFTs into the subset pool and borrows - _drawDebtNoLupCheck( - { - from: _borrower2, - borrower: _borrower2, - amountToBorrow: 5_000 * 1e18, - limitIndex: 5000, - tokenIds: tokenIdsToAdd - } - ); + _drawDebtNoLupCheck({ + from: _borrower2, + borrower: _borrower2, + amountToBorrow: 5_000 * 1e18, + limitIndex: 5000, + tokenIds: tokenIdsToAdd + }); /*****************************/ /*** Assert pre-kick state ***/ @@ -102,24 +93,21 @@ contract ERC721PoolLiquidationsSettleTest is ERC721HelperContract { interestRateUpdate: _startTime }) ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 5_004.807692307692310000 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 2_627.524038461538462750 * 1e18, - borrowerCollateralization: 1.543977154129479546 * 1e18 - } - ); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 5_004.807692307692310000 * 1e18, - borrowerCollateral: 3 * 1e18, - borrowert0Np: 1_751.682692307692308500 * 1e18, - borrowerCollateralization: 2.315965731194219318 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 5_004.807692307692310000 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 2_627.524038461538462750 * 1e18, + borrowerCollateralization: 1.543977154129479546 * 1e18 + }); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 5_004.807692307692310000 * 1e18, + borrowerCollateral: 3 * 1e18, + borrowert0Np: 1_751.682692307692308500 * 1e18, + borrowerCollateralization: 2.315965731194219318 * 1e18 + }); + assertEq(_quote.balanceOf(address(_pool)), 6_000 * 1e18); assertEq(_quote.balanceOf(_lender), 104_000 * 1e18); assertEq(_quote.balanceOf(_borrower), 5_100 * 1e18); @@ -130,34 +118,31 @@ contract ERC721PoolLiquidationsSettleTest is ERC721HelperContract { /*** Kick both loans ***/ /***********************/ - _kickWithDeposit( - { - from: _lender, - index: 2500, - borrower: _borrower, - debt: 5_067.367788461538463875 * 1e18, - collateral: 2 * 1e18, - bond: 1_501.442307692307693000 * 1e18, - removedFromDeposit: 1_501.442307692307693000 * 1e18, - transferAmount: 0, - lup: 3_863.654368867279344664 * 1e18 - } - ); - _kickWithDeposit( - { - from: _lender, - index: 2500, - borrower: _borrower2, - debt: 5_067.367788461538463875 * 1e18, - collateral: 3 * 1e18, - bond: 1_501.442307692307693000 * 1e18, - removedFromDeposit: 1_501.442307692307693000 * 1e18, - transferAmount: 0, - lup: 3_863.654368867279344664 * 1e18 - } - ); + _kickWithDeposit({ + from: _lender, + index: 2500, + borrower: _borrower, + debt: 5_067.367788461538463875 * 1e18, + collateral: 2 * 1e18, + bond: 1_501.442307692307693000 * 1e18, + removedFromDeposit: 1_501.442307692307693000 * 1e18, + transferAmount: 0, + lup: 3_863.654368867279344664 * 1e18 + }); - // skip to make loans clearable + _kickWithDeposit({ + from: _lender, + index: 2500, + borrower: _borrower2, + debt: 5_067.367788461538463875 * 1e18, + collateral: 3 * 1e18, + bond: 1_501.442307692307693000 * 1e18, + removedFromDeposit: 1_501.442307692307693000 * 1e18, + transferAmount: 0, + lup: 3_863.654368867279344664 * 1e18 + }); + + // skip to make loans clearable skip(80 hours); _assertPool( PoolParams({ @@ -176,24 +161,21 @@ contract ERC721PoolLiquidationsSettleTest is ERC721HelperContract { interestRateUpdate: _startTime }) ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 5_069.682183392068152308 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 2_627.524038461538462750 * 1e18, - borrowerCollateralization: 1.524219558190194493 * 1e18 - } - ); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 5_069.682183392068152308 * 1e18, - borrowerCollateral: 3 * 1e18, - borrowert0Np: 1_751.682692307692308500 * 1e18, - borrowerCollateralization: 2.286329337285291739 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 5_069.682183392068152308 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 2_627.524038461538462750 * 1e18, + borrowerCollateralization: 1.524219558190194493 * 1e18 + }); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 5_069.682183392068152308 * 1e18, + borrowerCollateral: 3 * 1e18, + borrowert0Np: 1_751.682692307692308500 * 1e18, + borrowerCollateralization: 2.286329337285291739 * 1e18 + }); + assertEq(_quote.balanceOf(address(_pool)), 6_000 * 1e18); assertEq(_quote.balanceOf(_lender), 104_000 * 1e18); assertEq(_quote.balanceOf(_borrower), 5_100 * 1e18); @@ -220,14 +202,22 @@ contract ERC721PoolLiquidationsSettleTest is ERC721HelperContract { neutralPrice: 1_751.682692307692308500 * 1e18 }) ); - _settle( - { - from: _lender, - borrower: _borrower2, - maxDepth: 1, - settledDebt: 5_067.367788461538463875 * 1e18 - } - ); + + // revert if auction is not settled + _assertMergeRemoveCollateralAuctionNotClearedRevert({ + from: _lender, + toIndex: MAX_FENWICK_INDEX, + noOfNFTsToRemove: 2, + removeCollateralAtIndex: new uint256[](0) + }); + + _settle({ + from: _lender, + borrower: _borrower2, + maxDepth: 1, + settledDebt: 5_067.367788461538463875 * 1e18 + }); + _assertAuction( AuctionParams({ borrower: _borrower2, @@ -245,31 +235,21 @@ contract ERC721PoolLiquidationsSettleTest is ERC721HelperContract { }) ); + // revert if auction is not settled + _assertMergeRemoveCollateralAuctionNotClearedRevert({ + from: _lender, + toIndex: MAX_FENWICK_INDEX, + noOfNFTsToRemove: 2, + removeCollateralAtIndex: new uint256[](0) + }); + // settle borrower - _settle( - { - from: _lender, - borrower: _borrower, - maxDepth: 5, - settledDebt: 5_067.367788461538463875 * 1e18 - } - ); - _assertAuction( - AuctionParams({ - borrower: _borrower, - active: false, - kicker: address(0), - bondSize: 0, - bondFactor: 0, - kickTime: 0, - kickMomp: 0, - totalBondEscrowed: 0, - auctionPrice: 0, - debtInAuction: 0, - thresholdPrice: 0, - neutralPrice: 0 - }) - ); + _settle({ + from: _lender, + borrower: _borrower, + maxDepth: 5, + settledDebt: 5_067.367788461538463875 * 1e18 + }); _assertPool( PoolParams({ @@ -288,77 +268,85 @@ contract ERC721PoolLiquidationsSettleTest is ERC721HelperContract { interestRateUpdate: _startTime + 80 hours }) ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 0, - borrowerCollateral: 0, - borrowert0Np: 2_627.524038461538462750 * 1e18, - borrowerCollateralization: 1 * 1e18 - } - ); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 0, - borrowerCollateral: 1 * 1e18, - borrowert0Np: 1_751.682692307692308500 * 1e18, - borrowerCollateralization: 1 * 1e18 - } + _assertAuction( + AuctionParams({ + borrower: _borrower, + active: false, + kicker: address(0), + bondSize: 0, + bondFactor: 0, + kickTime: 0, + kickMomp: 0, + totalBondEscrowed: 0, + auctionPrice: 0, + debtInAuction: 0, + thresholdPrice: 0, + neutralPrice: 0 + }) ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 0, + borrowerCollateral: 0, + borrowert0Np: 2_627.524038461538462750 * 1e18, + borrowerCollateralization: 1 * 1e18 + }); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 0, + borrowerCollateral: 1 * 1e18, + borrowert0Np: 1_751.682692307692308500 * 1e18, + borrowerCollateralization: 1 * 1e18 + }); // assert bucket used for settle - _assertBucket( - { - index: MAX_FENWICK_INDEX, - lpBalance: 0.000000137345389190751978670 * 1e27, - collateral: 1.375706158271934624 * 1e18, - deposit: 0, - exchangeRate: 0.999999999999999999994537736 * 1e27 - } - ); + _assertBucket({ + index: MAX_FENWICK_INDEX, + lpBalance: 0.000000137345389190 * 1e18, + collateral: 1.375706158271934624 * 1e18, + deposit: 0, + exchangeRate: 1.000000000007280914 * 1e18 + }); + assertEq(_quote.balanceOf(address(_pool)), 6_000 * 1e18); assertEq(_quote.balanceOf(_lender), 104_000 * 1e18); assertEq(_quote.balanceOf(_borrower), 5_100 * 1e18); assertEq(_quote.balanceOf(_borrower2), 13_000 * 1e18); // lender adds liquidity in min bucket and merge / removes 2 NFTs - _addLiquidity( - { - from: _lender, - amount: 100 * 1e18, - index: MAX_FENWICK_INDEX, - lpAward: 100.000000000000000000546226400 * 1e27, - newLup: MAX_PRICE - } - ); + _addLiquidity({ + from: _lender, + amount: 100 * 1e18, + index: MAX_FENWICK_INDEX, + lpAward: 99.999999999271908600 * 1e18, + newLup: MAX_PRICE + }); + uint256[] memory removalIndexes = new uint256[](3); removalIndexes[0] = 2500; removalIndexes[1] = 2501; removalIndexes[2] = MAX_FENWICK_INDEX; - _mergeOrRemoveCollateral( - { - from: _lender, - toIndex: MAX_FENWICK_INDEX, - noOfNFTsToRemove: 2, - collateralMerged: 2 * 1e18, - removeCollateralAtIndex: removalIndexes, - toIndexLps: 0 - } - ); + _mergeOrRemoveCollateral({ + from: _lender, + toIndex: MAX_FENWICK_INDEX, + noOfNFTsToRemove: 2, + collateralMerged: 2 * 1e18, + removeCollateralAtIndex: removalIndexes, + toIndexLps: 0 + }); + // lender merge and claim one more NFT removalIndexes = new uint256[](2); removalIndexes[0] = 2500; removalIndexes[1] = MAX_FENWICK_INDEX; - _mergeOrRemoveCollateral( - { - from: _lender, - toIndex: MAX_FENWICK_INDEX, - noOfNFTsToRemove: 1, - collateralMerged: 1 * 1e18, - removeCollateralAtIndex: removalIndexes, - toIndexLps: 0 - } - ); + _mergeOrRemoveCollateral({ + from: _lender, + toIndex: MAX_FENWICK_INDEX, + noOfNFTsToRemove: 1, + collateralMerged: 1 * 1e18, + removeCollateralAtIndex: removalIndexes, + toIndexLps: 0 + }); + // lender claims one more settled NFT _pool.removeCollateral(1, MAX_FENWICK_INDEX); @@ -381,15 +369,13 @@ contract ERC721PoolLiquidationsSettleTest is ERC721HelperContract { // check borrower 2 owner of 1 NFT assertEq(_collateral.ownerOf(51), _borrower2); - _assertBucket( - { - index: 2500, - lpBalance: 1_861.033884081553472671582113012 * 1e27, - collateral: 0, - deposit: 1861.636634299022017158 * 1e18, - exchangeRate: 1.000323879227898104734699503 * 1e27 - } - ); + _assertBucket({ + index: 2500, + lpBalance: 1_861.033884081553472950 * 1e18, + collateral: 0, + deposit: 1_861.636634299022017158 * 1e18, + exchangeRate: 1.000323879227898105 * 1e18 + }); } function testKickAndSettleSubsetPoolByRepay() external tearDown { @@ -397,16 +383,16 @@ contract ERC721PoolLiquidationsSettleTest is ERC721HelperContract { assertEq(_collateral.ownerOf(51), address(_pool)); assertEq(_collateral.ownerOf(53), address(_pool)); assertEq(_collateral.ownerOf(73), address(_pool)); + // borrower 2 repays debt and settles auction - _repayDebtNoLupCheck( - { - from: _borrower2, - borrower: _borrower2, - amountToRepay: 6_000 * 1e18, - amountRepaid: 0, - collateralToPull: 3 - } - ); + _repayDebtNoLupCheck({ + from: _borrower2, + borrower: _borrower2, + amountToRepay: 6_000 * 1e18, + amountRepaid: 0, + collateralToPull: 3 + }); + _assertAuction( AuctionParams({ borrower: _borrower2, @@ -423,34 +409,32 @@ contract ERC721PoolLiquidationsSettleTest is ERC721HelperContract { neutralPrice: 0 }) ); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 0, - borrowerCollateral: 0, - borrowert0Np: 0, - borrowerCollateralization: 1 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 0, + borrowerCollateral: 0, + borrowert0Np: 0, + borrowerCollateralization: 1 * 1e18 + }); + // after settle: NFTs pledged by auctioned borrower are owned by the borrower assertEq(_collateral.ownerOf(51), address(_borrower2)); assertEq(_collateral.ownerOf(53), address(_borrower2)); assertEq(_collateral.ownerOf(73), address(_borrower2)); - // before auction settle: NFTs pledged by auctioned borrower are owned by the pool assertEq(_collateral.ownerOf(1), address(_pool)); assertEq(_collateral.ownerOf(3), address(_pool)); + // borrower repays debt and settles auction - _repayDebtNoLupCheck( - { - from: _borrower, - borrower: _borrower, - amountToRepay: 6_000 * 1e18, - amountRepaid: 0, - collateralToPull: 2 - } - ); + _repayDebtNoLupCheck({ + from: _borrower, + borrower: _borrower, + amountToRepay: 6_000 * 1e18, + amountRepaid: 0, + collateralToPull: 2 + }); + _assertAuction( AuctionParams({ borrower: _borrower, @@ -467,15 +451,14 @@ contract ERC721PoolLiquidationsSettleTest is ERC721HelperContract { neutralPrice: 0 }) ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 0, - borrowerCollateral: 0, - borrowert0Np: 0, - borrowerCollateralization: 1 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 0, + borrowerCollateral: 0, + borrowert0Np: 0, + borrowerCollateralization: 1 * 1e18 + }); + // after settle: NFTs pledged by auctioned borrower are owned by the borrower assertEq(_collateral.ownerOf(1), address(_borrower)); assertEq(_collateral.ownerOf(3), address(_borrower)); diff --git a/tests/forge/ERC721Pool/ERC721PoolLiquidationsSettleAuction.t.sol b/tests/forge/ERC721Pool/ERC721PoolLiquidationsSettleAuction.t.sol new file mode 100644 index 000000000..24f55ccc2 --- /dev/null +++ b/tests/forge/ERC721Pool/ERC721PoolLiquidationsSettleAuction.t.sol @@ -0,0 +1,1288 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.14; + +import { ERC721Pool } from 'src/ERC721Pool.sol'; + +import { ERC721HelperContract } from "./ERC721DSTestPlus.sol"; + +import 'src/libraries/helpers/PoolHelper.sol'; + +contract ERC721PoolLiquidationsSettleAuctionTest is ERC721HelperContract { + + address internal _borrower; + address internal _borrower2; + address internal _lender; + + function setUp() external { + _borrower = makeAddr("borrower"); + _borrower2 = makeAddr("borrower2"); + _lender = makeAddr("lender"); + + // deploy subset pool + uint256[] memory subsetTokenIds = new uint256[](9); + subsetTokenIds[0] = 1; + subsetTokenIds[1] = 2; + subsetTokenIds[2] = 3; + subsetTokenIds[3] = 4; + subsetTokenIds[4] = 5; + subsetTokenIds[5] = 6; + subsetTokenIds[6] = 51; + subsetTokenIds[7] = 53; + subsetTokenIds[8] = 73; + _pool = _deploySubsetPool(subsetTokenIds); + + _mintAndApproveQuoteTokens(_lender, 120_000 * 1e18); + _mintAndApproveQuoteTokens(_borrower, 100 * 1e18); + _mintAndApproveQuoteTokens(_borrower2, 8_000 * 1e18); + + _mintAndApproveCollateralTokens(_borrower, 6); + _mintAndApproveCollateralTokens(_borrower2, 74); + + // Lender adds Quote token in one bucket + _addInitialLiquidity({ + from: _lender, + amount: 8_000 * 1e18, + index: 2500 + }); + _addInitialLiquidity({ + from: _lender, + amount: 2_000 * 1e18, + index: 2501 + }); + _addInitialLiquidity({ + from: _lender, + amount: 2_000 * 1e18, + index: 2502 + }); + _addInitialLiquidity({ + from: _lender, + amount: 1_000 * 1e18, + index: 2503 + }); + + // first borrower adds collateral token and borrows + uint256[] memory tokenIdsToAdd = new uint256[](2); + tokenIdsToAdd[0] = 1; + tokenIdsToAdd[1] = 3; + // borrower deposits two NFTs into the subset pool and borrows + _drawDebtNoLupCheck({ + from: _borrower, + borrower: _borrower, + amountToBorrow: 5_000 * 1e18, + limitIndex: 5000, + tokenIds: tokenIdsToAdd + }); + + // second borrower deposits three NFTs into the subset pool and borrows + tokenIdsToAdd = new uint256[](3); + tokenIdsToAdd[0] = 51; + tokenIdsToAdd[1] = 53; + tokenIdsToAdd[2] = 73; + // borrower deposits two NFTs into the subset pool and borrows + _drawDebtNoLupCheck({ + from: _borrower2, + borrower: _borrower2, + amountToBorrow: 5_000 * 1e18, + limitIndex: 5000, + tokenIds: tokenIdsToAdd + }); + + // kick both loans + _kickWithDeposit({ + from: _lender, + index: 2500, + borrower: _borrower, + debt: 5_067.367788461538463875 * 1e18, + collateral: 2 * 1e18, + bond: 1_501.442307692307693000 * 1e18, + removedFromDeposit: 1_501.442307692307693000 * 1e18, + transferAmount: 0, + lup: 3_825.305679430983794766 * 1e18 + }); + _kickWithDeposit({ + from: _lender, + index: 2500, + borrower: _borrower2, + debt: 5_067.367788461538463875 * 1e18, + collateral: 3 * 1e18, + bond: 1_501.442307692307693000 * 1e18, + removedFromDeposit: 1_501.442307692307693000 * 1e18, + transferAmount: 0, + lup: 99836282890 + }); + } + + function testSettlePartialDebtSubsetPool() external tearDown { + // the 2 token ids are owned by borrower before settle + assertEq(ERC721Pool(address(_pool)).borrowerTokenIds(_borrower, 0), 1); + assertEq(ERC721Pool(address(_pool)).borrowerTokenIds(_borrower, 1), 3); + + // skip to make loans clearable + skip(80 hours); + + _assertBucket({ + index: 2500, + lpBalance: 4_997.115384615384614 * 1e18, + collateral: 0, + deposit: 4_997.115384615384614 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 5_069.682183392068152308 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 2_627.524038461538462750 * 1e18, + borrowerCollateralization: 0.000000000039385618 * 1e18 + }); + + // first settle call settles partial borrower debt + _settle({ + from: _lender, + borrower: _borrower, + maxDepth: 1, + settledDebt: 4_997.146788514113366576 * 1e18 + }); + + // collateral in bucket used to settle auction increased with the amount used to settle debt + _assertBucket({ + index: 2500, + lpBalance: 4_997.115384615384614 * 1e18, + collateral: 1.293963857643160539 * 1e18, + deposit: 0, + exchangeRate: 1.000463012547417693 * 1e18 + }); + // partial borrower debt is settled, borrower collateral decreased with the amount used to settle debt + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 70.253071652712624346 * 1e18, + borrowerCollateral: 0.706036142356839461 * 1e18, + borrowert0Np: 2_627.524038461538462750 * 1e18, + borrowerCollateralization: 0.000000001003344372 * 1e18 + }); + + _assertCollateralInvariants(); + + // the 2 token ids are rebalanced and transferred to pool claimable tokens array after settle + assertEq(ERC721Pool(address(_pool)).bucketTokenIds(0), 3); + assertEq(ERC721Pool(address(_pool)).bucketTokenIds(1), 1); + + // all NFTs are owned by the pool + assertEq(_collateral.ownerOf(1), address(_pool)); + assertEq(_collateral.ownerOf(3), address(_pool)); + assertEq(_collateral.ownerOf(51), address(_pool)); + assertEq(_collateral.ownerOf(53), address(_pool)); + assertEq(_collateral.ownerOf(73), address(_pool)); + + // adding more liquidity to settle all auctions + _addLiquidityNoEventCheck({ + from: _lender, + amount: 20_000 * 1e18, + index: 2500 + }); + + _assertBucket({ + index: 2500, + lpBalance: 24_987.859419295112503013 * 1e18, + collateral: 1.293963857643160539 * 1e18, + deposit: 20_000 * 1e18, + exchangeRate: 1.000463012547417693 * 1e18 + }); + + _settle({ + from: _lender, + borrower: _borrower, + maxDepth: 1, + settledDebt: 70.220999947425097299 * 1e18 + }); + + + _assertBucket({ + index: 2500, + lpBalance: 24_987.859419295112503013 * 1e18, + collateral: 1.312146920864032689 * 1e18, + deposit: 19_929.746928347287375654 * 1e18, + exchangeRate: 1.000463012547417693 * 1e18 + }); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 0, + borrowerCollateral: 0, + borrowert0Np: 2_627.524038461538462750 * 1e18, + borrowerCollateralization: 1 * 1e18 + }); + + _assertCollateralInvariants(); + + _settle({ + from: _lender, + borrower: _borrower2, + maxDepth: 1, + settledDebt: 5_067.367788461538463875 * 1e18 + }); + + _assertBucket({ + index: 2500, + lpBalance: 24_987.859419295112503013 * 1e18, + collateral: 2.624293841728065377 * 1e18, + deposit: 14_860.064744955219223346 * 1e18, + exchangeRate: 1.000463012547417693 * 1e18 + }); + _assertBucket({ + index: 7388, + lpBalance: 0.000000137345389190 * 1e18, + collateral: 1.375706158271934623 * 1e18, + deposit: 0, + exchangeRate: 1.000000000007280914 * 1e18 + }); + // borrower 2 can claim 1 NFT token (id 51) after settle + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 0, + borrowerCollateral: 1 * 1e18, + borrowert0Np: 1_769.243311298076895206 * 1e18, + borrowerCollateralization: 1 * 1e18 + }); + + _assertCollateralInvariants(); + + assertEq(ERC721Pool(address(_pool)).borrowerTokenIds(_borrower2, 0), 51); + + // tokens used to settle auction are moved to pool claimable array + assertEq(ERC721Pool(address(_pool)).bucketTokenIds(0), 3); + assertEq(ERC721Pool(address(_pool)).bucketTokenIds(1), 1); + assertEq(ERC721Pool(address(_pool)).bucketTokenIds(2), 73); + assertEq(ERC721Pool(address(_pool)).bucketTokenIds(3), 53); + + // lender can claim 2 NFTs from bucket 2500 + changePrank(_lender); + _pool.removeCollateral(2, 2500); + + // lender adds liquidity in min bucket and merge / removes the other 2 NFTs + _addLiquidity({ + from: _lender, + amount: 100 * 1e18, + index: MAX_FENWICK_INDEX, + lpAward: 99.999999999271908600 * 1e18, + newLup: MAX_PRICE + }); + + uint256[] memory removalIndexes = new uint256[](2); + removalIndexes[0] = 2500; + removalIndexes[1] = MAX_FENWICK_INDEX; + _mergeOrRemoveCollateral({ + from: _lender, + toIndex: MAX_FENWICK_INDEX, + noOfNFTsToRemove: 2, + collateralMerged: 2 * 1e18, + removeCollateralAtIndex: removalIndexes, + toIndexLps: 0 + }); + + // the 4 NFTs claimed from pool are owned by lender + assertEq(_collateral.ownerOf(1), _lender); + assertEq(_collateral.ownerOf(3), _lender); + assertEq(_collateral.ownerOf(53), _lender); + assertEq(_collateral.ownerOf(73), _lender); + assertEq(_collateral.ownerOf(51), address(_pool)); + + // borrower 2 can pull 1 NFT from pool + _repayDebtNoLupCheck({ + from: _borrower2, + borrower: _borrower2, + amountToRepay: 0, + amountRepaid: 0, + collateralToPull: 1 + }); + + assertEq(_collateral.ownerOf(1), _lender); + assertEq(_collateral.ownerOf(3), _lender); + assertEq(_collateral.ownerOf(53), _lender); + assertEq(_collateral.ownerOf(73), _lender); + // the NFT pulled from pool is owned by lender + assertEq(_collateral.ownerOf(51), _borrower2); + + _assertBucket({ + index: 2500, + lpBalance: 14_853.187532758404035619 * 1e18, + collateral: 0, + deposit: 14_860.064744955219223346 * 1e18, + exchangeRate: 1.000463012547417693 * 1e18 + }); + _assertBucket({ + index: MAX_FENWICK_INDEX, + lpBalance: 99.999999999271908600 * 1e18, + collateral: 0, + deposit: 100 * 1e18, + exchangeRate: 1.000000000007280914 * 1e18 + }); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 0, + borrowerCollateral: 0, + borrowert0Np: 2_627.524038461538462750 * 1e18, + borrowerCollateralization: 1 * 1e18 + }); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 0, + borrowerCollateral: 0, + borrowert0Np: 0, + borrowerCollateralization: 1 * 1e18 + }); + + _assertCollateralInvariants(); + } + + function testDepositTakeAndSettleSubsetPool() external tearDown { + + // the 2 token ids are owned by borrower before settle + assertEq(ERC721Pool(address(_pool)).borrowerTokenIds(_borrower, 0), 1); + assertEq(ERC721Pool(address(_pool)).borrowerTokenIds(_borrower, 1), 3); + + _assertBucket({ + index: 2500, + lpBalance: 4997.115384615384614 * 1e18, + collateral: 0, + deposit: 4_997.115384615384614 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 5_067.367788461538463875 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 2_627.524038461538462750 * 1e18, + borrowerCollateralization: 0.000000000039403606 * 1e18 + }); + + skip(32 hours); + + _depositTake({ + from: _lender, + borrower: _borrower, + index: 2500 + }); + + _assertBucket({ + index: 2500, + lpBalance: 7_138.736263736263734674 * 1e18, + collateral: 1.848006454703595366 * 1e18, + deposit: 0, + exchangeRate: 1.000185179648802797 * 1e18 + }); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 425.033210305552211086 * 1e18, + borrowerCollateral: 0.151993545296404634 * 1e18, + borrowert0Np: 2_627.524038461538462750 * 1e18, + borrowerCollateralization: 0.000000000035701847 * 1e18 + }); + + _assertCollateralInvariants(); + + // borrower tries to repay remaining debt + _repayDebtNoLupCheck({ + from: _borrower2, + borrower: _borrower2, + amountToRepay: 1000 * 1e18, + amountRepaid: 0, + collateralToPull: 0 + }); + + skip(80 hours); + + _settle({ + from: _lender, + borrower: _borrower, + maxDepth: 1, + settledDebt: 424.955585758182281304 * 1e18 + }); + + _assertBucket({ + index: 2500, + lpBalance: 7_138.736263736263734674 * 1e18, + collateral: 1.848006454703595366 * 1e18, + deposit: 0, + exchangeRate: 1.000185179648802797 * 1e18 + }); + _assertBucket({ + index: 2501, + lpBalance: 2_000 * 1e18, + collateral: 0.110613668792155518 * 1e18, + deposit: 1_575.963420924493188203 * 1e18, + exchangeRate: 1.000605085927545054 * 1e18 + }); + _assertBucket({ + index: MAX_FENWICK_INDEX, + lpBalance: 4131213057, + collateral: 0.041379876504249116 * 1e18, + deposit: 0, + exchangeRate: 1 * 1e18 + }); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 0, + borrowerCollateral: 0, + borrowert0Np: 2_627.524038461538462750 * 1e18, + borrowerCollateralization: 1 * 1e18 + }); + + _assertCollateralInvariants(); + + // tokens used to settle auction are moved to pool claimable array + assertEq(ERC721Pool(address(_pool)).bucketTokenIds(0), 3); + assertEq(ERC721Pool(address(_pool)).bucketTokenIds(1), 1); + + // lender adds liquidity in min bucket and merge / removes the other 2 NFTs + _addLiquidity({ + from: _lender, + amount: 100 * 1e18, + index: MAX_FENWICK_INDEX, + lpAward: 100 * 1e18, + newLup: 3_806.274307891526195092 * 1e18 + }); + + uint256[] memory removalIndexes = new uint256[](3); + removalIndexes[0] = 2500; + removalIndexes[1] = 2501; + removalIndexes[2] = MAX_FENWICK_INDEX; + _mergeOrRemoveCollateral({ + from: _lender, + toIndex: MAX_FENWICK_INDEX, + noOfNFTsToRemove: 2, + collateralMerged: 2 * 1e18, + removeCollateralAtIndex: removalIndexes, + toIndexLps: 0 + }); + + // the 2 NFTs claimed from pool are owned by lender + assertEq(_collateral.ownerOf(1), _lender); + assertEq(_collateral.ownerOf(3), _lender); + + _assertCollateralInvariants(); + } + + function testDepositTakeAndSettleByPledgeSubsetPool() external tearDown { + + // the 2 token ids are owned by borrower before settle + assertEq(ERC721Pool(address(_pool)).borrowerTokenIds(_borrower, 0), 1); + assertEq(ERC721Pool(address(_pool)).borrowerTokenIds(_borrower, 1), 3); + + _assertBucket({ + index: 2500, + lpBalance: 4_997.115384615384614000 * 1e18, + collateral: 0, + deposit: 4_997.115384615384614000 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 5_067.367788461538463875 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 2_627.524038461538462750 * 1e18, + borrowerCollateralization: 0.000000000039403606 * 1e18 + }); + + skip(32 hours); + + _depositTake({ + from: _lender, + borrower: _borrower, + kicker: _lender, + index: 2500, + collateralArbed: 1.848006454703595366 * 1e18, + quoteTokenAmount: 7_140.058212410478208121 * 1e18, + bondChange: 2_142.017463723143462436 * 1e18, + isReward: true, + lpAwardTaker: 0, + lpAwardKicker: 2_141.620879120879120674 * 1e18 + }); + + _assertBucket({ + index: 2500, + lpBalance: 7_138.736263736263734674 * 1e18, + collateral: 1.848006454703595366 * 1e18, + deposit: 0, + exchangeRate: 1.000185179648802797 * 1e18 + }); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 425.033210305552211086 * 1e18, + borrowerCollateral: 0.151993545296404634 * 1e18, + borrowert0Np: 2_627.524038461538462750 * 1e18, + borrowerCollateralization: 0.000000000035701847 * 1e18 + }); + + _assertCollateralInvariants(); + + // borrower 2 repays entire debt and pulls collateral + _repayDebt({ + from: _borrower2, + borrower: _borrower2, + amountToRepay: 6_000 * 1e18, + amountRepaid: 5_068.293419619520519499 * 1e18, + collateralToPull: 3, + newLup: 3_844.432207828138682757 * 1e18 + }); + + // borrower exits from auction by pledging more collateral + uint256[] memory tokenIdsToAdd = new uint256[](3); + tokenIdsToAdd[0] = 2; + tokenIdsToAdd[1] = 4; + tokenIdsToAdd[2] = 5; + _drawDebtNoLupCheck({ + from: _borrower, + borrower: _borrower, + amountToBorrow: 0, + limitIndex: 0, + tokenIds: tokenIdsToAdd + }); + + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 425.033210305552211086 * 1e18, + borrowerCollateral: 3 * 1e18, + borrowert0Np: 149.442714324960768925 * 1e18, + borrowerCollateralization: 27.135048141751657646 * 1e18 + }); + _assertAuction( + AuctionParams({ + borrower: _borrower, + active: false, + kicker: address(0), + bondSize: 0, + bondFactor: 0, + kickTime: 0, + kickMomp: 0, + totalBondEscrowed: 0, + auctionPrice: 0, + debtInAuction: 0, + thresholdPrice: 141.677736768517403695 * 1e18, + neutralPrice: 0 + }) + ); + + _assertCollateralInvariants(); + + // the 3 new token ids pledged are owned by borrower + assertEq(ERC721Pool(address(_pool)).borrowerTokenIds(_borrower, 0), 1); + assertEq(ERC721Pool(address(_pool)).borrowerTokenIds(_borrower, 1), 3); + assertEq(ERC721Pool(address(_pool)).borrowerTokenIds(_borrower, 2), 2); + // tokens used to settle auction are moved to pool claimable array + assertEq(ERC721Pool(address(_pool)).bucketTokenIds(0), 5); + assertEq(ERC721Pool(address(_pool)).bucketTokenIds(1), 4); + + _assertBucket({ + index: 2500, + lpBalance: 7_138.736263736263734674 * 1e18, + collateral: 1.848006454703595366 * 1e18, + deposit: 0, + exchangeRate: 1.000185179648802797 * 1e18 + }); + _assertBucket({ + index: 6113, + lpBalance: 0.000008766823996015 * 1e18, + collateral: 0.151993545296404634 * 1e18, + deposit: 0, + exchangeRate: 1 * 1e18 + }); + + // lender adds liquidity in bucket 6113 and merge / removes the other 2 NFTs + _addLiquidity({ + from: _lender, + amount: 1000 * 1e18, + index: 6113, + lpAward: 1_000 * 1e18, + newLup: 3_844.432207828138682757 * 1e18 + }); + uint256[] memory removalIndexes = new uint256[](2); + removalIndexes[0] = 2500; + removalIndexes[1] = 6113; + _mergeOrRemoveCollateral({ + from: _lender, + toIndex: 6113, + noOfNFTsToRemove: 2, + collateralMerged: 2 * 1e18, + removeCollateralAtIndex: removalIndexes, + toIndexLps: 0 + }); + + // borrower repays entire debt and pulls collateral + _repayDebt({ + from: _borrower, + borrower: _borrower, + amountToRepay: 500 * 1e18, + amountRepaid: 425.033210305552211086 * 1e18, + collateralToPull: 3, + newLup: MAX_PRICE + }); + // borrower removes tokens from auction price bucket for compensated collateral fraction + _removeAllLiquidity({ + from: _borrower, + amount: 0.000008757551393712 * 1e18, + index: 6113, + newLup: MAX_PRICE, + lpRedeem: 0.000008766823996015 * 1e18 + }); + + // the 3 NFTs pulled from pool are owned by borrower + assertEq(_collateral.ownerOf(1), _borrower); + assertEq(_collateral.ownerOf(2), _borrower); + assertEq(_collateral.ownerOf(3), _borrower); + // the 2 NFTs claimed from pool are owned by lender + assertEq(_collateral.ownerOf(5), _lender); + assertEq(_collateral.ownerOf(4), _lender); + + _assertCollateralInvariants(); + } + + function testDepositTakeAndSettleByRepaySubsetPool() external tearDown { + + // the 2 token ids are owned by borrower before settle + assertEq(ERC721Pool(address(_pool)).borrowerTokenIds(_borrower, 0), 1); + assertEq(ERC721Pool(address(_pool)).borrowerTokenIds(_borrower, 1), 3); + + _assertBucket({ + index: 2500, + lpBalance: 4997.115384615384614 * 1e18, + collateral: 0, + deposit: 4_997.115384615384614000 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 5_067.367788461538463875 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 2_627.524038461538462750 * 1e18, + borrowerCollateralization: 0.000000000039403606 * 1e18 + }); + + skip(32 hours); + + _depositTake({ + from: _lender, + borrower: _borrower, + kicker: _lender, + index: 2500, + collateralArbed: 1.848006454703595366 * 1e18, + quoteTokenAmount: 7_140.058212410478208121 * 1e18, + bondChange: 2_142.017463723143462436 * 1e18, + isReward: true, + lpAwardTaker: 0, + lpAwardKicker: 2_141.620879120879120674 * 1e18 + }); + + _assertBucket({ + index: 2500, + lpBalance: 7_138.736263736263734674 * 1e18, + collateral: 1.848006454703595366 * 1e18, + deposit: 0, + exchangeRate: 1.000185179648802797 * 1e18 + }); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 425.033210305552211086 * 1e18, + borrowerCollateral: 0.151993545296404634 * 1e18, + borrowert0Np: 2_627.524038461538462750 * 1e18, + borrowerCollateralization: 0.000000000035701847 * 1e18 + }); + + _assertCollateralInvariants(); + + // borrower 2 repays entire debt and pulls collateral + _repayDebt({ + from: _borrower2, + borrower: _borrower2, + amountToRepay: 6_000 * 1e18, + amountRepaid: 5_068.293419619520519499 * 1e18, + collateralToPull: 3, + newLup: 3_844.432207828138682757 * 1e18 + }); + // borrower exits from auction by repaying the debt + _repayDebt({ + from: _borrower, + borrower: _borrower, + amountToRepay: 500 * 1e18, + amountRepaid: 425.033210305552211086 * 1e18, + collateralToPull: 0, + newLup: MAX_PRICE + }); + + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 0, + borrowerCollateral: 0, + borrowert0Np: 0, + borrowerCollateralization: 1 * 1e18 + }); + _assertAuction( + AuctionParams({ + borrower: _borrower, + active: false, + kicker: address(0), + bondSize: 0, + bondFactor: 0, + kickTime: 0, + kickMomp: 0, + totalBondEscrowed: 0, + auctionPrice: 0, + debtInAuction: 0, + thresholdPrice: 0, + neutralPrice: 0 + }) + ); + + _assertCollateralInvariants(); + + // tokens used to settle auction are moved to pool claimable array + assertEq(ERC721Pool(address(_pool)).bucketTokenIds(0), 3); + assertEq(ERC721Pool(address(_pool)).bucketTokenIds(1), 1); + + _assertBucket({ + index: 2500, + lpBalance: 7_138.736263736263734674 * 1e18, + collateral: 1.848006454703595366 * 1e18, + deposit: 0, + exchangeRate: 1.000185179648802797 * 1e18 + }); + _assertBucket({ + index: 6113, + lpBalance: 0.000008766823996015 * 1e18, + collateral: 0.151993545296404634 * 1e18, + deposit: 0, + exchangeRate: 1 * 1e18 + }); + + // lender adds liquidity in bucket 6113 and merge / removes the other 2 NFTs + _addLiquidity({ + from: _lender, + amount: 1000 * 1e18, + index: 6113, + lpAward: 1_000 * 1e18, + newLup: MAX_PRICE + }); + uint256[] memory removalIndexes = new uint256[](2); + removalIndexes[0] = 2500; + removalIndexes[1] = 6113; + _mergeOrRemoveCollateral({ + from: _lender, + toIndex: 6113, + noOfNFTsToRemove: 2, + collateralMerged: 2 * 1e18, + removeCollateralAtIndex: removalIndexes, + toIndexLps: 0 + }); + + // the 2 NFTs claimed from pool are owned by lender + assertEq(_collateral.ownerOf(3), _lender); + assertEq(_collateral.ownerOf(1), _lender); + + _assertCollateralInvariants(); + + // borrower removes tokens from auction price bucket for compensated collateral fraction + _removeAllLiquidity({ + from: _borrower, + amount: 0.000008757551393712 * 1e18, + index: 6113, + newLup: MAX_PRICE, + lpRedeem: 0.000008766823996015 * 1e18 + }); + + } + + function testDepositTakeAndSettleByRegularTakeSubsetPool() external tearDown { + + // the 2 token ids are owned by borrower before settle + assertEq(ERC721Pool(address(_pool)).borrowerTokenIds(_borrower, 0), 1); + assertEq(ERC721Pool(address(_pool)).borrowerTokenIds(_borrower, 1), 3); + + _assertBucket({ + index: 2502, + lpBalance: 2_000 * 1e18, + collateral: 0, + deposit: 2_000 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 5_067.367788461538463875 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 2_627.524038461538462750 * 1e18, + borrowerCollateralization: 0.000000000039403606 * 1e18 + }); + + skip(4 hours); + + _addLiquidity({ + from: _lender, + amount: 1_000 * 1e18, + index: 2000, + lpAward: 1_000 * 1e18, + newLup: 3_806.274307891526195092 * 1e18 + }); + + _depositTake({ + from: _lender, + borrower: _borrower, + kicker: _lender, + index: 2000, + collateralArbed: 0.021378186081598093 * 1e18, + quoteTokenAmount: 999.9999999999999908 * 1e18, + bondChange: 299.99999999999999724 * 1e18, + isReward: false, + lpAwardTaker: 0, + lpAwardKicker: 0 + }); + + _assertBucket({ + index: 2000, + lpBalance: 1_000 * 1e18, + collateral: 0.021378186081598093 * 1e18, + deposit: 0, + exchangeRate: 0.999999999999999991 * 1e18 + }); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 4_422.207326928504959735 * 1e18, + borrowerCollateral: 1.978621813918401907 * 1e18, + borrowert0Np: 2_627.524038461538462750 * 1e18, + borrowerCollateralization: 1.703035796058482710 * 1e18 + }); + + _assertCollateralInvariants(); + + assertEq(_quote.balanceOf(_borrower), 5_100 * 1e18); + assertEq(_quote.balanceOf(address(_pool)), 4_000 * 1e18); + + // borrower exits from auction by regular take + _take({ + from: _lender, + borrower: _borrower, + maxCollateral: 1, + bondChange: 1_201.442307692307695760 * 1e18, + givenAmount: 4_422.207326928504959735 * 1e18, + collateralTaken: 0.286141493566424944 * 1e18, + isReward: false + }); + + assertEq(_quote.balanceOf(_borrower), 16_132.410148540612418451 * 1e18); + assertEq(_quote.balanceOf(address(_pool)), 8_422.207326928504959735 * 1e18); + + // borrower 2 repays entire debt and pulls collateral + _repayDebt({ + from: _borrower2, + borrower: _borrower2, + amountToRepay: 6_000 * 1e18, + amountRepaid: 5_067.483483110752298817 * 1e18, + collateralToPull: 3, + newLup: MAX_PRICE + }); + + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 0, + borrowerCollateral: 0, + borrowert0Np: 0, + borrowerCollateralization: 1 * 1e18 + }); + _assertAuction( + AuctionParams({ + borrower: _borrower, + active: false, + kicker: address(0), + bondSize: 0, + bondFactor: 0, + kickTime: 0, + kickMomp: 0, + totalBondEscrowed: 0, + auctionPrice: 0, + debtInAuction: 0, + thresholdPrice: 0, + neutralPrice: 0 + }) + ); + + _assertCollateralInvariants(); + + // remaining token is moved to pool claimable array + assertEq(ERC721Pool(address(_pool)).bucketTokenIds(0), 1); + + _assertBucket({ + index: 2000, + lpBalance: 1_000 * 1e18, + collateral: 0.021378186081598093 * 1e18, + deposit: 0, + exchangeRate: 0.999999999999999991 * 1e18 + }); + _assertBucket({ + index: 2222, + lpBalance: 15_127.888999922350308085 * 1e18, + collateral: 0.978621813918401907 * 1e18, + deposit: 0, + exchangeRate: 1 * 1e18 + }); + + // lender adds liquidity in bucket 2222 and merge / removes remaining NFTs + _addLiquidity({ + from: _lender, + amount: 20_000 * 1e18, + index: 2222, + lpAward: 20_000 * 1e18, + newLup: MAX_PRICE + }); + _addLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2000, + lpAward: 10_000.00000000000009 * 1e18, + newLup: MAX_PRICE + }); + uint256[] memory removalIndexes = new uint256[](2); + removalIndexes[0] = 2000; + removalIndexes[1] = 2222; + _mergeOrRemoveCollateral({ + from: _lender, + toIndex: 2222, + noOfNFTsToRemove: 1, + collateralMerged: 1 * 1e18, + removeCollateralAtIndex: removalIndexes, + toIndexLps: 0 + }); + + // the 2 NFTs (one taken, one claimed) are owned by lender + assertEq(_collateral.ownerOf(3), _lender); + assertEq(_collateral.ownerOf(1), _lender); + + _assertCollateralInvariants(); + + // borrower removes tokens from auction price bucket for compensated collateral fraction + _removeAllLiquidity({ + from: _borrower, + amount: 15_113.342952807040348884 * 1e18, + index: 2222, + newLup: MAX_PRICE, + lpRedeem: 15_127.888999922350308085 * 1e18 + }); + } + + function testDepositTakeAndSettleByBucketTakeSubsetPool() external tearDown { + + // the 2 token ids are owned by borrower before settle + assertEq(ERC721Pool(address(_pool)).borrowerTokenIds(_borrower, 0), 1); + assertEq(ERC721Pool(address(_pool)).borrowerTokenIds(_borrower, 1), 3); + + _assertBucket({ + index: 2502, + lpBalance: 2_000 * 1e18, + collateral: 0, + deposit: 2_000 * 1e18, + exchangeRate: 1 * 1e18 + }); + + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 5_067.367788461538463875 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 2_627.524038461538462750 * 1e18, + borrowerCollateralization: 0.000000000039403606 * 1e18 + }); + + skip(32 hours); + + _depositTake({ + from: _lender, + borrower: _borrower, + kicker: _lender, + index: 2502, + collateralArbed: 0.747044074730990508 * 1e18, + quoteTokenAmount: 2_857.671941853722277733 * 1e18, + bondChange: 857.301582556116683320 * 1e18, + isReward: true, + lpAwardTaker: 0, + lpAwardKicker: 857.142857142857143034 * 1e18 + }); + + _assertBucket({ + index: 2502, + lpBalance: 2_857.142857142857143034 * 1e18, + collateral: 0.747044074730990508 * 1e18, + deposit: 0, + exchangeRate: 1.000185179648802797 * 1e18 + }); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 3_422.703599695281361864 * 1e18, + borrowerCollateral: 1.252955925269009492 * 1e18, + borrowert0Np: 2_627.524038461538462750 * 1e18, + borrowerCollateralization: 0.000000000036547267 * 1e18 + }); + + _assertCollateralInvariants(); + + // borrower 2 repays entire debt and pulls collateral + _repayDebt({ + from: _borrower2, + borrower: _borrower2, + amountToRepay: 6_000 * 1e18, + amountRepaid: 5_068.293419619520519499 * 1e18, + collateralToPull: 3, + newLup: 3_863.654368867279344664 * 1e18 + }); + + // borrower exits from auction by bucket take: lender adds quote token at a higher priced bucket and calls deposit take + _addLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2400, + lpAward: 10_000 * 1e18, + newLup: 6_362.157913642177655049 * 1e18 + }); + + _depositTake({ + from: _lender, + borrower: _borrower, + kicker: _lender, + index: 2400, + collateralArbed: 0.768540585971418892 * 1e18, + quoteTokenAmount: 4_889.576570993259088377 * 1e18, + bondChange: 1_466.872971297977726513 * 1e18, + isReward: true, + lpAwardTaker: 0, + lpAwardKicker: 1_466.872971297977726513 * 1e18 + }); + + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 0, + borrowerCollateral: 0, + borrowert0Np: 0, + borrowerCollateralization: 1 * 1e18 + }); + _assertAuction( + AuctionParams({ + borrower: _borrower, + active: false, + kicker: address(0), + bondSize: 0, + bondFactor: 0, + kickTime: 0, + kickMomp: 0, + totalBondEscrowed: 0, + auctionPrice: 0, + debtInAuction: 0, + thresholdPrice: 0, + neutralPrice: 0 + }) + ); + + _assertCollateralInvariants(); + + // tokens used to settle auction are moved to pool claimable array + assertEq(ERC721Pool(address(_pool)).bucketTokenIds(0), 3); + assertEq(ERC721Pool(address(_pool)).bucketTokenIds(1), 1); + + _assertBucket({ + index: 2400, + lpBalance: 11_466.872971297977726513 * 1e18, + collateral: 0.768540585971418892 * 1e18, + deposit: 6_577.296400304718638136 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertBucket({ + index: 6113, + lpBalance: 0.000027940555056533 * 1e18, + collateral: 0.484415339297590600 * 1e18, + deposit: 0, + exchangeRate: 1 * 1e18 + }); + + // lender adds liquidity in bucket 6113 and merge / removes the other 2 NFTs + _addLiquidity({ + from: _lender, + amount: 1_000 * 1e18, + index: 6113, + lpAward: 1_000 * 1e18, + newLup: MAX_PRICE + }); + uint256[] memory removalIndexes = new uint256[](3); + removalIndexes[0] = 2400; + removalIndexes[1] = 2502; + removalIndexes[2] = 6113; + _mergeOrRemoveCollateral({ + from: _lender, + toIndex: 6113, + noOfNFTsToRemove: 2, + collateralMerged: 2 * 1e18, + removeCollateralAtIndex: removalIndexes, + toIndexLps: 0 + }); + + // the 2 NFTs claimed from pool are owned by lender + assertEq(_collateral.ownerOf(3), _lender); + assertEq(_collateral.ownerOf(1), _lender); + + _assertCollateralInvariants(); + + // borrower removes tokens from auction price bucket for compensated collateral fraction + _removeAllLiquidity({ + from: _borrower, + amount: 0.000027911002546377 * 1e18, + index: 6113, + newLup: MAX_PRICE, + lpRedeem: 0.000027940555056533 * 1e18 + }); + } + + function testDepositTakeAndSettleBySettleSubsetPool() external { + + // the 2 token ids are owned by borrower before settle + assertEq(ERC721Pool(address(_pool)).borrowerTokenIds(_borrower, 0), 1); + assertEq(ERC721Pool(address(_pool)).borrowerTokenIds(_borrower, 1), 3); + + _assertBucket({ + index: 2502, + lpBalance: 2_000 * 1e18, + collateral: 0, + deposit: 2_000 * 1e18, + exchangeRate: 1 * 1e18 + }); + + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 5_067.367788461538463875 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 2_627.524038461538462750 * 1e18, + borrowerCollateralization: 0.000000000039403606 * 1e18 + }); + + skip(32 hours); + + _depositTake({ + from: _lender, + borrower: _borrower, + kicker: _lender, + index: 2502, + collateralArbed: 0.747044074730990508 * 1e18, + quoteTokenAmount: 2_857.671941853722277733 * 1e18, + bondChange: 857.301582556116683320 * 1e18, + isReward: true, + lpAwardTaker: 0, + lpAwardKicker: 857.142857142857143034 * 1e18 + }); + + _assertBucket({ + index: 2502, + lpBalance: 2_857.142857142857143034 * 1e18, + collateral: 0.747044074730990508 * 1e18, + deposit: 0, + exchangeRate: 1.000185179648802797 * 1e18 + }); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 3_422.703599695281361864 * 1e18, + borrowerCollateral: 1.252955925269009492 * 1e18, + borrowert0Np: 2_627.524038461538462750 * 1e18, + borrowerCollateralization: 0.000000000036547267 * 1e18 + }); + + _assertCollateralInvariants(); + + // borrower 2 repays entire debt and pulls collateral + _repayDebt({ + from: _borrower2, + borrower: _borrower2, + amountToRepay: 6_000 * 1e18, + amountRepaid: 5_068.293419619520519499 * 1e18, + collateralToPull: 3, + newLup: 3_863.654368867279344664 * 1e18 + }); + + skip(72 hours); + + // borrower exits from auction by pool debt settle + _settle({ + from: _lender, + borrower: _borrower, + maxDepth: 10, + settledDebt: 3_422.078505440842334340 * 1e18 + }); + + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 0, + borrowerCollateral: 0, + borrowert0Np: 2_627.524038461538462750 * 1e18, + borrowerCollateralization: 1 * 1e18 + }); + _assertAuction( + AuctionParams({ + borrower: _borrower, + active: false, + kicker: address(0), + bondSize: 0, + bondFactor: 0, + kickTime: 0, + kickMomp: 0, + totalBondEscrowed: 0, + auctionPrice: 0, + debtInAuction: 0, + thresholdPrice: 0, + neutralPrice: 0 + }) + ); + + _assertCollateralInvariants(); + + // tokens used to settle auction are moved to pool claimable array + assertEq(ERC721Pool(address(_pool)).bucketTokenIds(0), 3); + assertEq(ERC721Pool(address(_pool)).bucketTokenIds(1), 1); + + _assertBucket({ + index: 2500, + lpBalance: 4_997.115384615384614000 * 1e18, + collateral: 0.886272650740532744 * 1e18, + deposit: 1_574.636172339194134488 * 1e18, + exchangeRate: 1.000354601931047794 * 1e18 + }); + _assertBucket({ + index: 2502, + lpBalance: 2_857.142857142857143034 * 1e18, + collateral: 0.747044074730990508 * 1e18, + deposit: 0, + exchangeRate: 1.000185179648802797 * 1e18 + }); + _assertBucket({ + index: 7388, + lpBalance: 0.000000036608295127 * 1e18, + collateral: 0.366683274528476748 * 1e18, + deposit: 0, + exchangeRate: 1 * 1e18 + }); + + // lender adds liquidity in bucket 7388 and merge / removes the other 2 NFTs + _addLiquidity({ + from: _lender, + amount: 1_000 * 1e18, + index: 7388, + lpAward: 1_000 * 1e18, + newLup: MAX_PRICE + }); + uint256[] memory removalIndexes = new uint256[](3); + removalIndexes[0] = 2500; + removalIndexes[1] = 2502; + removalIndexes[2] = 7388; + _mergeOrRemoveCollateral({ + from: _lender, + toIndex: 7388, + noOfNFTsToRemove: 2, + collateralMerged: 2 * 1e18, + removeCollateralAtIndex: removalIndexes, + toIndexLps: 0 + }); + + // the 2 NFTs claimed from pool are owned by lender + assertEq(_collateral.ownerOf(3), _lender); + assertEq(_collateral.ownerOf(1), _lender); + + _assertCollateralInvariants(); + } +} diff --git a/tests/forge/ERC721Pool/ERC721PoolLiquidationsTake.t.sol b/tests/forge/ERC721Pool/ERC721PoolLiquidationsTake.t.sol index 939607332..59531d696 100644 --- a/tests/forge/ERC721Pool/ERC721PoolLiquidationsTake.t.sol +++ b/tests/forge/ERC721Pool/ERC721PoolLiquidationsTake.t.sol @@ -3,6 +3,8 @@ pragma solidity 0.8.14; import { ERC721HelperContract } from "./ERC721DSTestPlus.sol"; +import { NFTNoopTakeExample } from "../interactions/NFTTakeExample.sol"; + import 'src/libraries/helpers/PoolHelper.sol'; contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { @@ -10,11 +12,13 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { address internal _borrower; address internal _borrower2; address internal _lender; + address internal _withdrawRecipient; function setUp() external { - _borrower = makeAddr("borrower"); - _borrower2 = makeAddr("borrower2"); - _lender = makeAddr("lender"); + _borrower = makeAddr("borrower"); + _borrower2 = makeAddr("borrower2"); + _lender = makeAddr("lender"); + _withdrawRecipient = makeAddr("withdrawRecipient"); // deploy subset pool uint256[] memory subsetTokenIds = new uint256[](6); @@ -34,84 +38,66 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { _mintAndApproveCollateralTokens(_borrower2, 74); // Lender adds Quote token accross 5 prices - _addInitialLiquidity( - { - from: _lender, - amount: 2_000 * 1e18, - index: _i9_91 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 5_000 * 1e18, - index: _i9_81 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 11_000 * 1e18, - index: _i9_72 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 25_000 * 1e18, - index: _i9_62 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 30_000 * 1e18, - index: _i9_52 - } - ); + _addInitialLiquidity({ + from: _lender, + amount: 2_000 * 1e18, + index: _i9_91 + }); + _addInitialLiquidity({ + from: _lender, + amount: 5_000 * 1e18, + index: _i9_81 + }); + _addInitialLiquidity({ + from: _lender, + amount: 11_000 * 1e18, + index: _i9_72 + }); + _addInitialLiquidity({ + from: _lender, + amount: 25_000 * 1e18, + index: _i9_62 + }); + _addInitialLiquidity({ + from: _lender, + amount: 30_000 * 1e18, + index: _i9_52 + }); - // first borrower adds collateral token and borrows + // first borrower adds collateral token and borrows uint256[] memory tokenIdsToAdd = new uint256[](2); tokenIdsToAdd[0] = 1; tokenIdsToAdd[1] = 3; // borrower deposits two NFTs into the subset pool and borrows - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - tokenIds: tokenIdsToAdd - } - ); - _borrow( - { - from: _borrower, - amount: 19.8 * 1e18, - indexLimit: _i9_91, - newLup: 9.917184843435912074 * 1e18 - } - ); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + tokenIds: tokenIdsToAdd + }); + _borrow({ + from: _borrower, + amount: 19.8 * 1e18, + indexLimit: _i9_91, + newLup: 9.917184843435912074 * 1e18 + }); // second borrower deposits three NFTs into the subset pool and borrows tokenIdsToAdd = new uint256[](3); tokenIdsToAdd[0] = 51; tokenIdsToAdd[1] = 53; tokenIdsToAdd[2] = 73; - _pledgeCollateral( - { - from: _borrower2, - borrower: _borrower2, - tokenIds: tokenIdsToAdd - } - ); - _borrow( - { - from: _borrower2, - amount: 15 * 1e18, - indexLimit: _i9_72, - newLup: 9.917184843435912074 * 1e18 - } - ); + _pledgeCollateral({ + from: _borrower2, + borrower: _borrower2, + tokenIds: tokenIdsToAdd + }); + _borrow({ + from: _borrower2, + amount: 15 * 1e18, + indexLimit: _i9_72, + newLup: 9.917184843435912074 * 1e18 + }); /*****************************/ /*** Assert pre-kick state ***/ @@ -134,24 +120,21 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { interestRateUpdate: _startTime }) ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 19.819038461538461548 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.404995192307692312 * 1e18, - borrowerCollateralization: 1.000773560501591181 * 1e18 - } - ); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 15.014423076923076930 * 1e18, - borrowerCollateral: 3 * 1e18, - borrowert0Np: 5.255048076923076925 * 1e18, - borrowerCollateralization: 1.981531649793150539 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 19.819038461538461548 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.404995192307692312 * 1e18, + borrowerCollateralization: 1.000773560501591181 * 1e18 + }); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 15.014423076923076930 * 1e18, + borrowerCollateral: 3 * 1e18, + borrowert0Np: 5.255048076923076925 * 1e18, + borrowerCollateralization: 1.981531649793150539 * 1e18 + }); + assertEq(_quote.balanceOf(_lender), 47_000 * 1e18); } @@ -177,26 +160,22 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { }) ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 22.728719829841718804 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.404995192307692312 * 1e18, - borrowerCollateralization: 0.872656701977127996 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 22.728719829841718804 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.404995192307692312 * 1e18, + borrowerCollateralization: 0.872656701977127996 * 1e18 + }); - _kick( - { - from: _lender, - borrower: _borrower, - debt: 23.012828827714740289 * 1e18, - collateral: 2 * 1e18, - bond: 0.227287198298417188 * 1e18, - transferAmount: 0.227287198298417188 * 1e18 - } - ); + _kick({ + from: _lender, + borrower: _borrower, + debt: 23.012828827714740289 * 1e18, + collateral: 2 * 1e18, + bond: 0.227287198298417188 * 1e18, + transferAmount: 0.227287198298417188 * 1e18 + }); /******************************/ /*** Assert Post-kick state ***/ @@ -219,25 +198,23 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { interestRateUpdate: block.timestamp }) ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 23.012828827714740289 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.404995192307692312 * 1e18, - borrowerCollateralization: 0.861883162446546169 * 1e18 - } - ); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 17.218727143819483942 * 1e18, - borrowerCollateral: 3 * 1e18, - borrowert0Np: 5.255048076923076925 * 1e18, - borrowerCollateralization: 1.727860269914713433 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 23.012828827714740289 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.404995192307692312 * 1e18, + borrowerCollateralization: 0.861883162446546169 * 1e18 + }); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 17.218727143819483942 * 1e18, + borrowerCollateral: 3 * 1e18, + borrowert0Np: 5.255048076923076925 * 1e18, + borrowerCollateralization: 1.727860269914713433 * 1e18 + }); + assertEq(_quote.balanceOf(_lender), 46_999.772712801701582812 * 1e18); + _assertAuction( AuctionParams({ borrower: _borrower, @@ -254,22 +231,22 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { neutralPrice: 11.932577910666902372 * 1e18 }) ); - _assertKicker( - { - kicker: _lender, - claimable: 0, - locked: 0.227287198298417188 * 1e18 - } - ); + _assertKicker({ + kicker: _lender, + claimable: 0, + locked: 0.227287198298417188 * 1e18 + }); skip(5.5 hours); // before take: NFTs pledged by auctioned borrower are owned by the pool assertEq(_collateral.ownerOf(3), address(_pool)); assertEq(_collateral.ownerOf(1), address(_pool)); + // before take: check quote token balances of taker and borrower assertEq(_quote.balanceOf(_lender), 46_999.772712801701582812 * 1e18); assertEq(_quote.balanceOf(_borrower), 119.8 * 1e18); + _assertAuction( AuctionParams({ borrower: _borrower, @@ -287,15 +264,13 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { }) ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 23.013479028125331754 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.404995192307692312 * 1e18, - borrowerCollateralization: 0.861858811639550854 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 23.013479028125331754 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.404995192307692312 * 1e18, + borrowerCollateralization: 0.861858811639550854 * 1e18 + }); uint256 snapshot = vm.snapshot(); @@ -303,17 +278,15 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { /* Take partial collateral tokens (1) ***/ /****************************************/ - _take( - { - from: _lender, - borrower: _borrower, - maxCollateral: 1, - bondChange: 0.168752135153387434 * 1e18, - givenAmount: 16.875213515338743424 * 1e18, - collateralTaken: 1.0 * 1e18, - isReward: false - } - ); + _take({ + from: _lender, + borrower: _borrower, + maxCollateral: 1, + bondChange: 0.168752135153387434 * 1e18, + givenAmount: 16.875213515338743424 * 1e18, + collateralTaken: 1.0 * 1e18, + isReward: false + }); _assertPool( PoolParams({ @@ -333,15 +306,13 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { }) ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 7.749209044755361552 * 1e18, - borrowerCollateral: 1 * 1e18, - borrowert0Np: 7.061045370627448273 * 1e18, - borrowerCollateralization: 1.279767365438131935 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 7.749209044755361552 * 1e18, + borrowerCollateral: 1 * 1e18, + borrowert0Np: 7.061045370627448273 * 1e18, + borrowerCollateralization: 1.279767365438131935 * 1e18 + }); _assertAuction( AuctionParams({ @@ -359,18 +330,16 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { neutralPrice: 0 }) ); - - _assertKicker( - { - kicker: address(0), - claimable: 0, - locked: 0 - } - ); + _assertKicker({ + kicker: address(0), + claimable: 0, + locked: 0 + }); // after take: one NFT pledged by liquidated borrower is owned by the taker assertEq(_collateral.ownerOf(3), _lender); assertEq(_collateral.ownerOf(1), address(_pool)); + // after take: check quote token balances of taker and borrower assertEq(_quote.balanceOf(_lender), 46_982.897499286362839388 * 1e18); assertEq(_quote.balanceOf(_borrower), 119.8 * 1e18); // no additional tokens as there is no rounding of collateral taken (1) @@ -381,17 +350,15 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { /*** Take all collateral tokens (2) ***/ /**************************************/ - _take( - { - from: _lender, - borrower: _borrower, - maxCollateral: 2, - bondChange: 0.227287198298417188 * 1e18, - givenAmount: 24.624422560094104976 * 1e18, - collateralTaken: 1.459206577606363895 * 1e18, // not a rounded collateral, difference of 2 - 1.16 collateral should go to borrower in quote tokens at auction price - isReward: false - } - ); + _take({ + from: _lender, + borrower: _borrower, + maxCollateral: 2, + bondChange: 0.227287198298417188 * 1e18, + givenAmount: 24.624422560094104976 * 1e18, + collateralTaken: 1.459206577606363895 * 1e18, // not a rounded collateral, difference of 2 - 1.16 collateral should go to borrower in quote tokens at auction price + isReward: false + }); _assertPool( PoolParams({ @@ -411,15 +378,13 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { }) ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 0, - borrowerCollateral: 0, - borrowert0Np: 0, - borrowerCollateralization: 1 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 0, + borrowerCollateral: 0, + borrowert0Np: 0, + borrowerCollateralization: 1 * 1e18 + }); _assertAuction( AuctionParams({ @@ -437,18 +402,16 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { neutralPrice: 0 }) ); - - _assertKicker( - { - kicker: address(0), - claimable: 0, - locked: 0 * 1e18 - } - ); + _assertKicker({ + kicker: address(0), + claimable: 0, + locked: 0 * 1e18 + }); // after take: NFTs pledged by liquidated borrower are owned by the taker assertEq(_collateral.ownerOf(3), _lender); assertEq(_collateral.ownerOf(1), _lender); + // after take: check quote token balances of taker and borrower assertEq(_quote.balanceOf(_lender), 46_966.022285771024095971 * 1e18); assertEq(_quote.balanceOf(_borrower), 128.926004470583381865 * 1e18); // borrower gets quote tokens from the difference of rounded collateral (2) and needed collateral (1.16) at auction price (19.8) = 16.6 additional tokens @@ -475,27 +438,22 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { neutralPrice: 0 }) ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 22.728719829841718804 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.404995192307692312 * 1e18, + borrowerCollateralization: 0.872656701977127996 * 1e18 + }); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 22.728719829841718804 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.404995192307692312 * 1e18, - borrowerCollateralization: 0.872656701977127996 * 1e18 - } - ); - - _kick( - { - from: _lender, - borrower: _borrower, - debt: 23.012828827714740289 * 1e18, - collateral: 2 * 1e18, - bond: 0.227287198298417188 * 1e18, - transferAmount: 0.227287198298417188 * 1e18 - } - ); + _kick({ + from: _lender, + borrower: _borrower, + debt: 23.012828827714740289 * 1e18, + collateral: 2 * 1e18, + bond: 0.227287198298417188 * 1e18, + transferAmount: 0.227287198298417188 * 1e18 + }); /******************************/ /*** Assert Post-kick state ***/ @@ -518,25 +476,23 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { interestRateUpdate: block.timestamp }) ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 23.012828827714740289 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.404995192307692312 * 1e18, - borrowerCollateralization: 0.861883162446546169 * 1e18 - } - ); - _assertBorrower( - { - borrower: _borrower2, - borrowerDebt: 17.218727143819483942 * 1e18, - borrowerCollateral: 3 * 1e18, - borrowert0Np: 5.255048076923076925 * 1e18, - borrowerCollateralization: 1.727860269914713433 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 23.012828827714740289 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.404995192307692312 * 1e18, + borrowerCollateralization: 0.861883162446546169 * 1e18 + }); + _assertBorrower({ + borrower: _borrower2, + borrowerDebt: 17.218727143819483942 * 1e18, + borrowerCollateral: 3 * 1e18, + borrowert0Np: 5.255048076923076925 * 1e18, + borrowerCollateralization: 1.727860269914713433 * 1e18 + }); + assertEq(_quote.balanceOf(_lender), 46_999.772712801701582812 * 1e18); + _assertAuction( AuctionParams({ borrower: _borrower, @@ -553,19 +509,18 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { neutralPrice: 11.932577910666902372 * 1e18 }) ); - _assertKicker( - { - kicker: _lender, - claimable: 0, - locked: 0.227287198298417188 * 1e18 - } - ); + _assertKicker({ + kicker: _lender, + claimable: 0, + locked: 0.227287198298417188 * 1e18 + }); skip(10 hours); // before take: NFTs pledged by auctioned borrower are owned by the pool assertEq(_collateral.ownerOf(3), address(_pool)); assertEq(_collateral.ownerOf(1), address(_pool)); + // before take: check quote token balances of taker assertEq(_quote.balanceOf(_lender), 46_999.772712801701582812 * 1e18); @@ -585,36 +540,32 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { neutralPrice: 11.932577910666902372 * 1e18 }) ); - - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 23.014011023943546872 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.404995192307692312 * 1e18, - borrowerCollateralization: 0.861838888763733724 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 23.014011023943546872 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.404995192307692312 * 1e18, + borrowerCollateralization: 0.861838888763733724 * 1e18 + }); /**************************************/ /*** Take all collateral tokens (2) ***/ /**************************************/ - _take( - { - from: _lender, - borrower: _borrower, - maxCollateral: 2, - bondChange: 0.014915722388333628 * 1e18, - givenAmount: 1.491572238833362816 * 1e18, - collateralTaken: 2 * 1e18, - isReward: true - } - ); + _take({ + from: _lender, + borrower: _borrower, + maxCollateral: 2, + bondChange: 0.014915722388333628 * 1e18, + givenAmount: 1.491572238833362816 * 1e18, + collateralTaken: 2 * 1e18, + isReward: true + }); // after take: NFTs pledged by liquidated borrower are owned by the taker assertEq(_collateral.ownerOf(3), _lender); assertEq(_collateral.ownerOf(1), _lender); + // after take : Taker quote token used for buying collateral assertEq(_quote.balanceOf(_lender), 46_998.281140562868219996 * 1e18); @@ -637,16 +588,13 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { ); // Borrower collateral is 0 and some debt is still to be paid - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 23.148335279174565965 * 1e18, - borrowerCollateral: 0, - borrowert0Np: 10.404995192307692312 * 1e18, - borrowerCollateralization: 0 - } - ); - + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 23.148335279174565965 * 1e18, + borrowerCollateral: 0, + borrowert0Np: 10.404995192307692312 * 1e18, + borrowerCollateralization: 0 + }); _assertAuction( AuctionParams({ borrower: _borrower, @@ -663,24 +611,20 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { neutralPrice: 11.932577910666902372 * 1e18 }) ); - // kicker bond is locked as auction is not cleared - _assertKicker( - { - kicker: _lender, - claimable: 0, - locked: 0.242202920686750816 * 1e18 - } - ); + _assertKicker({ + kicker: _lender, + claimable: 0, + locked: 0.242202920686750816 * 1e18 + }); - _settle( - { - from: _lender, - borrower: _borrower, - maxDepth: 10, - settledDebt: 20.183898781290497858 * 1e18 - } - ); + // settle auction + _settle({ + from: _lender, + borrower: _borrower, + maxDepth: 10, + settledDebt: 20.183898781290497858 * 1e18 + }); _assertAuction( AuctionParams({ @@ -698,18 +642,24 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { neutralPrice: 0 }) ); + _assertKicker({ + kicker: _lender, + claimable: 0.242202920686750816 * 1e18, + locked: 0 + }); - _assertKicker( - { - kicker: _lender, - claimable: 0.242202920686750816 * 1e18, - locked: 0 - } - ); + uint256 snapshot = vm.snapshot(); - // Kicker claims bond + reward changePrank(_lender); - _pool.withdrawBonds(); + + // Kicker claims bond + reward and transfer to a different address + _pool.withdrawBonds(_withdrawRecipient); + assertEq(_quote.balanceOf(_withdrawRecipient), 0.242202920686750816 * 1e18); + + vm.revertTo(snapshot); + + // Kicker claims bond + reward + _pool.withdrawBonds(_lender); assertEq(_quote.balanceOf(_lender), 46_998.523343483554970812 * 1e18); } @@ -733,37 +683,30 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { neutralPrice: 0 }) ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 22.728719829841718804 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.404995192307692312 * 1e18, + borrowerCollateralization: 0.872656701977127996 * 1e18 + }); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 22.728719829841718804 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.404995192307692312 * 1e18, - borrowerCollateralization: 0.872656701977127996 * 1e18 - } - ); - - _kick( - { - from: _lender, - borrower: _borrower, - debt: 23.012828827714740289 * 1e18, - collateral: 2 * 1e18, - bond: 0.227287198298417188 * 1e18, - transferAmount: 0.227287198298417188 * 1e18 - } - ); + _kick({ + from: _lender, + borrower: _borrower, + debt: 23.012828827714740289 * 1e18, + collateral: 2 * 1e18, + bond: 0.227287198298417188 * 1e18, + transferAmount: 0.227287198298417188 * 1e18 + }); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 23.012828827714740289 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 10.404995192307692312 * 1e18, - borrowerCollateralization: 0.861883162446546169 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 23.012828827714740289 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 10.404995192307692312 * 1e18, + borrowerCollateralization: 0.861883162446546169 * 1e18 + }); _assertAuction( AuctionParams({ borrower: _borrower, @@ -784,17 +727,16 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { // skip enough time to accumulate debt and take to not settle auction skip(50 hours); - _take( - { - from: _lender, - borrower: _borrower, - maxCollateral: 1, - bondChange: 0.000000000000006781 * 1e18, - givenAmount: 0.000000000000678144 * 1e18, - collateralTaken: 1 * 1e18, - isReward: true - } - ); + _take({ + from: _lender, + borrower: _borrower, + maxCollateral: 1, + bondChange: 0.000000000000006781 * 1e18, + givenAmount: 0.000000000000678144 * 1e18, + collateralTaken: 1 * 1e18, + isReward: true + }); + _assertAuction( AuctionParams({ borrower: _borrower, @@ -839,15 +781,23 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { neutralPrice: 0 }) ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 0, - borrowerCollateral: 1 * 1e18, - borrowert0Np: 0, - borrowerCollateralization: 1 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 0, + borrowerCollateral: 1 * 1e18, + borrowert0Np: 0, + borrowerCollateralization: 1 * 1e18 + }); + + // borrower should be able to pull collateral from the pool + _repayDebtNoLupCheck({ + from: _borrower, + borrower: _borrower, + amountToRepay: 0, + amountRepaid: 0, + collateralToPull: 1 + }); + vm.revertTo(snapshot); // borrower repays part of debt, but not enough to exit from auction @@ -859,16 +809,15 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { collateralToPull: 0, newLup: _priceAt(3696) }); + // borrower pledge one more NFT to exit from auction uint256[] memory tokenIdsToAdd = new uint256[](1); tokenIdsToAdd[0] = 5; - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - tokenIds: tokenIdsToAdd - } - ); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + tokenIds: tokenIdsToAdd + }); _assertAuction( AuctionParams({ @@ -886,14 +835,51 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { neutralPrice: 0 }) ); - _assertBorrower( - { - borrower: _borrower, - borrowerDebt: 19.630052245331353428 * 1e18, - borrowerCollateral: 2 * 1e18, - borrowert0Np: 8.902861174861655548 * 1e18, - borrowerCollateralization: 1.010408400292926569 * 1e18 - } - ); + _assertBorrower({ + borrower: _borrower, + borrowerDebt: 19.630052245331353428 * 1e18, + borrowerCollateral: 2 * 1e18, + borrowert0Np: 8.902861174861655548 * 1e18, + borrowerCollateralization: 1.010408400292926569 * 1e18 + }); + + } + + function testTakeCollateralWithAtomicSwapSubsetPool() external tearDown { + // Skip to make borrower undercollateralized + skip(1000 days); + + _kick({ + from: _lender, + borrower: _borrower, + debt: 23.012828827714740289 * 1e18, + collateral: 2 * 1e18, + bond: 0.227287198298417188 * 1e18, + transferAmount: 0.227287198298417188 * 1e18 + }); + + skip(5.5 hours); + + uint256 initialBalance = 10_000 * 1e18; + + // instantiate a NOOP taker contract which implements IERC721Taker + NFTNoopTakeExample taker = new NFTNoopTakeExample(); + deal(address(_quote), address(taker), initialBalance); + changePrank(address(taker)); + _quote.approve(address(_pool), type(uint256).max); + + bytes memory data = abi.encode(address(_pool)); + _pool.take(_borrower, 2, address(taker), data); + + // check that token ids are the same as id pledged by borrower + assertEq(taker.tokenIdsReceived(0), 3); + assertEq(taker.tokenIdsReceived(1), 1); + + // check that the amount of quote tokens passed to taker contract is the same as the one deducted from taker balance + uint256 currentBalance = _quote.balanceOf(address(taker)); + assertEq(initialBalance - taker.quoteAmountDueReceived(), currentBalance); + + // check address received is the address of current ajna pool + assertEq(taker.poolAddressReceived(), address(_pool)); } } \ No newline at end of file diff --git a/tests/forge/ERC721Pool/ERC721PoolPurchaseQuote.t.sol b/tests/forge/ERC721Pool/ERC721PoolPurchaseQuote.t.sol index 18a868850..a4c80af8f 100644 --- a/tests/forge/ERC721Pool/ERC721PoolPurchaseQuote.t.sol +++ b/tests/forge/ERC721Pool/ERC721PoolPurchaseQuote.t.sol @@ -48,15 +48,13 @@ contract ERC721PoolPurchaseQuoteTest is ERC721HelperContract { assertEq(_priceAtTestIndex, 3_010.892022197881557845 * 1e18); // check bucket state - _assertBucket( - { - index: testIndex, - lpBalance: 0, - collateral: 0, - deposit: 0, - exchangeRate: 1 * 1e27 - } - ); + _assertBucket({ + index: testIndex, + lpBalance: 0, + collateral: 0, + deposit: 0, + exchangeRate: 1 * 1e18 + }); // check pool state assertEq(_collateral.balanceOf(_bidder), 13); @@ -66,59 +64,50 @@ contract ERC721PoolPurchaseQuoteTest is ERC721HelperContract { assertEq(_quote.balanceOf(_bidder), 0); // lender adds initial quote to pool - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: testIndex - } - ); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: testIndex + }); // check bucket state - _assertBucket( - { - index: testIndex, - lpBalance: 10_000 * 1e27, - collateral: 0, - deposit: 10_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); + _assertBucket({ + index: testIndex, + lpBalance: 10_000 * 1e18, + collateral: 0, + deposit: 10_000 * 1e18, + exchangeRate: 1 * 1e18 + }); // _bidder deposits collateral into a bucket changePrank(_bidder); + uint256[] memory tokenIdsToAdd = new uint256[](3); tokenIdsToAdd[0] = 65; tokenIdsToAdd[1] = 70; tokenIdsToAdd[2] = 73; - uint256 lpBalanceChange = _addCollateral( - { - from: _bidder, - tokenIds: tokenIdsToAdd, - index: testIndex, - lpAward: 9_032.676066593644673535 * 1e27 - } - ); + uint256 lpBalanceChange = _addCollateral({ + from: _bidder, + tokenIds: tokenIdsToAdd, + index: testIndex, + lpAward: 9_032.676066593644673535 * 1e18 + }); // check bucket state - _assertBucket( - { - index: testIndex, - lpBalance: 19_032.676066593644673535 * 1e27, - collateral: Maths.wad(3), - deposit: 10_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _bidder, - index: testIndex, - lpBalance: lpBalanceChange, - depositTime: _startTime - } - ); + _assertBucket({ + index: testIndex, + lpBalance: 19_032.676066593644673535 * 1e18, + collateral: Maths.wad(3), + deposit: 10_000 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _bidder, + index: testIndex, + lpBalance: lpBalanceChange, + depositTime: _startTime + }); // check pool state assertEq(_collateral.balanceOf(_bidder), 10); @@ -129,76 +118,65 @@ contract ERC721PoolPurchaseQuoteTest is ERC721HelperContract { // bidder removes quote token from bucket skip(1 days); // skip to avoid penalty + uint256 qtToRemove = Maths.wmul(_priceAtTestIndex, 3 * 1e18); - _removeAllLiquidity( - { - from: _bidder, - amount: qtToRemove, - index: testIndex, - newLup: _lup(), - lpRedeem: 9_032.676066593644673535 * 1e27 - } - ); + + _removeAllLiquidity({ + from: _bidder, + amount: qtToRemove, + index: testIndex, + newLup: _lup(), + lpRedeem: 9_032.676066593644673535 * 1e18 + }); + assertEq(_quote.balanceOf(_bidder), qtToRemove); - _assertBucket( - { - index: testIndex, - lpBalance: 10_000 * 1e27, - collateral: Maths.wad(3), - deposit: 967.323933406355326465 * 1e18, - exchangeRate: 1 * 1e27 - } - ); - _assertLenderLpBalance( - { - lender: _bidder, - index: testIndex, - lpBalance: 0, - depositTime: _startTime - } - ); + _assertBucket({ + index: testIndex, + lpBalance: 10_000 * 1e18, + collateral: Maths.wad(3), + deposit: 967.323933406355326465 * 1e18, + exchangeRate: 1 * 1e18 + }); + _assertLenderLpBalance({ + lender: _bidder, + index: testIndex, + lpBalance: 0, + depositTime: _startTime + }); // lender removes all collateral from bucket - _removeCollateral( - { - from: _lender, - amount: 3, - index: testIndex, - lpRedeem: 9_032.676066593644673535 * 1e27 - } - ); - - _assertBucket( - { - index: testIndex, - lpBalance: 967.323933406355326465 * 1e27, - collateral: 0, - deposit: 967.323933406355326465 * 1e18, - exchangeRate: 1 * 1e27 - } - ); + _removeCollateral({ + from: _lender, + amount: 3, + index: testIndex, + lpRedeem: 9_032.676066593644673535 * 1e18 + }); + + _assertBucket({ + index: testIndex, + lpBalance: 967.323933406355326465 * 1e18, + collateral: 0, + deposit: 967.323933406355326465 * 1e18, + exchangeRate: 1 * 1e18 + }); // lender removes remaining quote token to empty the bucket - _removeAllLiquidity( - { - from: _lender, - amount: 967.323933406355326465 * 1e18, - index: testIndex, - newLup: _lup(), - lpRedeem: 967.323933406355326465 * 1e27 - } - ); - - _assertBucket( - { - index: testIndex, - lpBalance: 0, - collateral: 0, - deposit: 0, - exchangeRate: 1 * 1e27 - } - ); + _removeAllLiquidity({ + from: _lender, + amount: 967.323933406355326465 * 1e18, + index: testIndex, + newLup: _lup(), + lpRedeem: 967.323933406355326465 * 1e18 + }); + + _assertBucket({ + index: testIndex, + lpBalance: 0, + collateral: 0, + deposit: 0, + exchangeRate: 1 * 1e18 + }); } /** @@ -210,42 +188,32 @@ contract ERC721PoolPurchaseQuoteTest is ERC721HelperContract { */ function testSubsetPurchaseQuoteWithDebt() external tearDown { // lenders add liquidity - _addInitialLiquidity( - { - from: _lender, - amount: 20_000 * 1e18, - index: 2350 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2351 - } - ); - _addInitialLiquidity( - { - from: _lender, - amount: 10_000 * 1e18, - index: 2352 - } - ); - - _addInitialLiquidity( - { - from: _lender2, - amount: 4_000 * 1e18, - index: 2350 - } - ); - _addInitialLiquidity( - { - from: _lender2, - amount: 5_000 * 1e18, - index: 2352 - } - ); + _addInitialLiquidity({ + from: _lender, + amount: 20_000 * 1e18, + index: 2350 + }); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2351 + }); + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: 2352 + }); + + _addInitialLiquidity({ + from: _lender2, + amount: 4_000 * 1e18, + index: 2350 + }); + _addInitialLiquidity({ + from: _lender2, + amount: 5_000 * 1e18, + index: 2352 + }); skip(3600); @@ -255,34 +223,30 @@ contract ERC721PoolPurchaseQuoteTest is ERC721HelperContract { tokenIdsToAdd[1] = 3; tokenIdsToAdd[2] = 5; tokenIdsToAdd[3] = 51; - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - tokenIds: tokenIdsToAdd - } - ); - _borrow( - { - from: _borrower, - amount: 24_000 * 1e18, - indexLimit: 2_351, - newLup: 8_123.467933811934300919 * 1e18 - } - ); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + tokenIds: tokenIdsToAdd + }); + _borrow({ + from: _borrower, + amount: 24_000 * 1e18, + indexLimit: 2_351, + newLup: 8_123.467933811934300919 * 1e18 + }); + assertEq(_lup(), _priceAt(2351)); + skip(86400); // check bucket state - _assertBucket( - { - index: 2350, - lpBalance: 24_000 * 1e27, - collateral: 0, - deposit: 24_000 * 1e18, - exchangeRate: 1 * 1e27 - } - ); + _assertBucket({ + index: 2350, + lpBalance: 24_000 * 1e18, + collateral: 0, + deposit: 24_000 * 1e18, + exchangeRate: 1 * 1e18 + }); // bidder purchases all quote from the highest bucket tokenIdsToAdd = new uint256[](4); @@ -292,102 +256,87 @@ contract ERC721PoolPurchaseQuoteTest is ERC721HelperContract { tokenIdsToAdd[3] = 74; uint256 amountToPurchase = 10_100 * 1e18; assertGt(_quote.balanceOf(address(_pool)), amountToPurchase); + uint256 amountWithInterest = 24_002.749104114061152000 * 1e18; - _addCollateral( - { - from: _bidder, - tokenIds: tokenIdsToAdd, - index: 2350, - lpAward: 32_654.410675370944354984500292928 * 1e27 - } - ); + _addCollateral({ + from: _bidder, + tokenIds: tokenIdsToAdd, + index: 2350, + lpAward: 32_654.410675370944354985 * 1e18 + }); + skip(25 hours); // remove liquidity after one day to avoid early withdraw penalty - _removeAllLiquidity( - { - from: _bidder, - amount: amountWithInterest, - index: 2350, - newLup: _priceAt(2352), - lpRedeem: 24_000.766696558404292700773653981 * 1e27 - } - ); + + _removeAllLiquidity({ + from: _bidder, + amount: amountWithInterest, + index: 2350, + newLup: _priceAt(2352), + lpRedeem: 24_000.766696558404301151 * 1e18 + }); assertEq(_quote.balanceOf(_bidder), amountWithInterest); // check bucket state - _assertBucket( - { - index: 2350, - lpBalance: 32_653.643978812540062283726638947 * 1e27, - collateral: Maths.wad(4), - deposit: 0, - exchangeRate: 1.000082597676179283352120528 * 1e27 - } - ); + _assertBucket({ + index: 2350, + lpBalance: 32_653.643978812540053834 * 1e18, + collateral: Maths.wad(4), + deposit: 0, + exchangeRate: 1.000082597676179284 * 1e18 + }); // bidder withdraws unused collateral - (uint256 amount) = _removeCollateral( - { - from: _bidder, - amount: 1, - index: 2350, - lpRedeem: 8_163.410994703135015570931665340 * 1e27 - } - ); - - _assertLenderLpBalance( - { - lender: _bidder, - index: 2350, - lpBalance: 490.232984109405046712794973607 * 1e27, - depositTime: _startTime + 25 hours - } - ); + (uint256 amount) = _removeCollateral({ + from: _bidder, + amount: 1, + index: 2350, + lpRedeem: 8_163.410994703135010282 * 1e18 + }); + + _assertLenderLpBalance({ + lender: _bidder, + index: 2350, + lpBalance: 490.232984109405043552 * 1e18, + depositTime: _startTime + 25 hours + }); skip(7200); changePrank(_lender); // lender exchanges their lp for collateral - (amount) = _removeCollateral( - { - from: _lender, - amount: 1, - index: 2350, - lpRedeem: 8_163.410994703135015570931665340 * 1e27 - } - ); - - _assertLenderLpBalance( - { - lender: _bidder, - index: 2350, - lpBalance: 490.232984109405046712794973607 * 1e27, - depositTime: _startTime + 25 hours - } - ); + (amount) = _removeCollateral({ + from: _lender, + amount: 1, + index: 2350, + lpRedeem: 8_163.410994703135018445 * 1e18 + }); + + _assertLenderLpBalance({ + lender: _bidder, + index: 2350, + lpBalance: 490.232984109405043552 * 1e18, + depositTime: _startTime + 25 hours + }); skip(3600); // check bucket state - _assertBucket( - { - index: 2350, - lpBalance: 16_326.821989406270031141863308267 * 1e27, - collateral: Maths.wad(2), - deposit: 0, - exchangeRate: 1.000082597676179283352120529 * 1e27 - } - ); + _assertBucket({ + index: 2350, + lpBalance: 16_326.821989406270025107 * 1e18, + collateral: Maths.wad(2), + deposit: 0, + exchangeRate: 1.000082597676179284 * 1e18 + }); // should revert if lender2 attempts to remove more collateral than lp is available for - _assertRemoveCollateralInsufficientLPsRevert( - { - from: _lender2, - amount: 1, - index: 2350 - } - ); + _assertRemoveCollateralInsufficientLPsRevert({ + from: _lender2, + amount: 1, + index: 2350 + }); skip(3600); } diff --git a/tests/forge/ERC721Pool/ERC721PoolReserveAuction.t.sol b/tests/forge/ERC721Pool/ERC721PoolReserveAuction.t.sol index 8a93dd435..ccbba4718 100644 --- a/tests/forge/ERC721Pool/ERC721PoolReserveAuction.t.sol +++ b/tests/forge/ERC721Pool/ERC721PoolReserveAuction.t.sol @@ -27,57 +27,51 @@ contract ERC721PoolReserveAuctionTest is ERC721HelperContract { // lender adds liquidity and borrower draws debt uint16 bucketId = 1663; - _addInitialLiquidity( - { - from: _lender, - amount: 200_000 * 1e18, - index: bucketId - } - ); + + _addInitialLiquidity({ + from: _lender, + amount: 200_000 * 1e18, + index: bucketId + }); // borrower draws debt uint256[] memory tokenIdsToAdd = new uint256[](1); tokenIdsToAdd[0] = 1; - _pledgeCollateral( - { - from: _borrower, - borrower: _borrower, - tokenIds: tokenIdsToAdd - } - ); - _borrow( - { - from: _borrower, - amount: 175_000 * 1e18, - indexLimit: bucketId, - newLup: 251_183.992399245533703810 * 1e18 - } - ); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + tokenIds: tokenIdsToAdd + }); + _borrow({ + from: _borrower, + amount: 175_000 * 1e18, + indexLimit: bucketId, + newLup: 251_183.992399245533703810 * 1e18 + }); + (uint256 poolDebt,,) = _pool.debtInfo(); assertEq(poolDebt - 175_000 * 1e18, 168.26923076923085 * 1e18); + skip(26 weeks); + (poolDebt,,) = _pool.debtInfo(); assertEq(poolDebt - 175_000 * 1e18, 4_590.373946590638353626 * 1e18); // debt matches develop } function testClaimableReserveNoAuction() external { // ensure empty state is returned - _assertReserveAuction( - { - reserves: 168.26923076923085 * 1e18, - claimableReserves : 0, - claimableReservesRemaining: 0, - auctionPrice: 0, - timeRemaining: 0 - } - ); + _assertReserveAuction({ + reserves: 168.26923076923085 * 1e18, + claimableReserves : 0, + claimableReservesRemaining: 0, + auctionPrice: 0, + timeRemaining: 0 + }); // ensure cannot take when no auction was started - _assertTakeReservesNoAuctionRevert( - { - amount: 555 * 1e18 - } - ); + _assertTakeReservesNoAuctionRevert({ + amount: 555 * 1e18 + }); } function testUnclaimableReserves() external { @@ -91,16 +85,16 @@ contract ERC721PoolReserveAuctionTest is ERC721HelperContract { newLup: 251_183.992399245533703810 * 1e18 }); - _assertReserveAuction( - { - reserves: 499.181304561658553626 * 1e18, - claimableReserves : 0, - claimableReservesRemaining: 0, - auctionPrice: 0, - timeRemaining: 0 - } - ); + _assertReserveAuction({ + reserves: 499.181304561658553626 * 1e18, + claimableReserves : 0, + claimableReservesRemaining: 0, + auctionPrice: 0, + timeRemaining: 0 + }); + changePrank(_bidder); + _assertTakeReservesNoReservesRevert(); } @@ -114,57 +108,70 @@ contract ERC721PoolReserveAuctionTest is ERC721HelperContract { collateralToPull: 0, newLup: MAX_PRICE }); - _assertReserveAuction( - { - reserves: 499.181304561658553626 * 1e18, - claimableReserves : 499.181304561658553626 * 1e18, - claimableReservesRemaining: 0, - auctionPrice: 0, - timeRemaining: 0 - } - ); + + _assertReserveAuction({ + reserves: 499.181304561658553626 * 1e18, + claimableReserves : 499.181304561658553626 * 1e18, + claimableReservesRemaining: 0, + auctionPrice: 0, + timeRemaining: 0 + }); // kick off a new auction - _startClaimableReserveAuction( - { - from: _bidder, - remainingReserves: 494.189491516041968090 * 1e18, - price: 1_000_000_000 * 1e18 - } - ); + _startClaimableReserveAuction({ + from: _bidder, + remainingReserves: 494.189491516041968090 * 1e18, + price: 1_000_000_000 * 1e18 + }); + _assertReserveAuctionPrice(1_000_000_000 * 1e18); // check prices skip(37 minutes); _assertReserveAuctionPrice(652176034.882782126826643053 * 1e18); + skip(23 hours); // 23 hours 37 minutes _assertReserveAuctionPrice(77.745441780421987394 * 1e18); + skip(1400); // 24 hours 0 minutes 20 seconds _assertReserveAuctionPrice(59.604644775390625 * 1e18); + skip(100); // 24 hours 2 minutes _assertReserveAuctionPrice(58.243272807255146201 * 1e18); + skip(58 minutes); // 25 hours _assertReserveAuctionPrice(29.8023223876953125 * 1e18); + skip(5 hours); // 30 hours _assertReserveAuctionPrice(0.931322574615478515 * 1e18); + skip(121 minutes); // 32 hours 1 minute _assertReserveAuctionPrice(0.230156355619639189 * 1e18); + skip(7700 seconds); // 34 hours 9 minutes 20 seconds _assertReserveAuctionPrice(0.052459681325756842 * 1e18); + skip(8 hours); // 42 hours 9 minutes 20 seconds _assertReserveAuctionPrice(0.000204920630178738 * 1e18); + skip(6 hours); // 42 hours 9 minutes 20 seconds _assertReserveAuctionPrice(0.000003201884846542 * 1e18); + skip(3100 seconds); // 43 hours _assertReserveAuctionPrice(0.000001755953640897 * 1e18); + skip(5 hours); // 48 hours _assertReserveAuctionPrice(0.000000054873551278 * 1e18); + skip(12 hours); // 60 hours _assertReserveAuctionPrice(0.000000000013396863 * 1e18); + skip(11 hours); // 71 hours _assertReserveAuctionPrice(0.000000000000006541 * 1e18); + skip(3599 seconds); // 71 hours 59 minutes 59 seconds _assertReserveAuctionPrice(0.000000000000003308 * 1e18); + skip(1 seconds); // 72 hours _assertReserveAuctionPrice(0.000000000000003270 * 1e18); } diff --git a/tests/forge/FenwickTree.t.sol b/tests/forge/FenwickTree.t.sol index 30359e338..49af985aa 100644 --- a/tests/forge/FenwickTree.t.sol +++ b/tests/forge/FenwickTree.t.sol @@ -109,11 +109,11 @@ contract FenwickTreeTest is DSTestPlus { function testFenwickFuzzyScalingPrefix( uint256 insertions_, uint256 totalAmount_, + uint256 seed_, uint256 scaleIndex_, uint256 factor_ - ) external { - - _tree.fuzzyFill(insertions_, totalAmount_, false); + ) external { + _tree.fuzzyFill(insertions_, totalAmount_, seed_, false); uint256 scaleIndex = bound(scaleIndex_, 2, MAX_INDEX); uint256 subIndex = randomInRange(1, scaleIndex - 1); @@ -158,11 +158,11 @@ contract FenwickTreeTest is DSTestPlus { function testLoadFenwickFuzzyScalingFind( uint256 insertions_, uint256 totalAmount_, + uint256 seed_, uint256 scaleIndex_, uint256 factor_ - ) external { - - _tree.fuzzyFill(insertions_, totalAmount_, false); + ) external { + _tree.fuzzyFill(insertions_, totalAmount_, seed_, false); uint256 scaleIndex = bound(scaleIndex_, 2, 7388); uint256 subIndex = randomInRange(0, scaleIndex - 1); @@ -190,10 +190,10 @@ contract FenwickTreeTest is DSTestPlus { */ function testLoadFenwickFuzzyRemoval( uint256 insertions_, - uint256 totalAmount_ - ) external { - - _tree.fuzzyFill(insertions_, totalAmount_, true); + uint256 totalAmount_, + uint256 seed_ + ) external { + _tree.fuzzyFill(insertions_, totalAmount_, seed_, true); // get Index randombly uint256 removalIndex = _tree.getIByInsertIndex(randomInRange(0, _tree.numInserts() - 1)); @@ -213,7 +213,6 @@ contract FenwickTreeTest is DSTestPlus { assertEq(preRemovalTreeSum - removalAmount, _tree.treeSum()); assertEq(preRemovalParentIndexSum - removalAmount, postRemovalParentIndexSum); } - } contract FenwickTreeGasLoadTest is DSTestPlus { diff --git a/tests/forge/Heap.t.sol b/tests/forge/Heap.t.sol index d537d0d70..b2f93ee85 100644 --- a/tests/forge/Heap.t.sol +++ b/tests/forge/Heap.t.sol @@ -231,10 +231,10 @@ contract HeapTest is DSTestPlus { assertEq(_loans.getTotalTps(), 7); } - function testLoadHeapFuzzy(uint256 inserts_) public { + function testLoadHeapFuzzy(uint256 inserts_, uint256 seed_) public { // test adding different TPs - _loans.fuzzyFill(inserts_, true); + _loans.fuzzyFill(inserts_, seed_, true); // test adding different TPs address removeAddress = _loans.getIdByInsertIndex(randomInRange(1, _loans.numInserts() - 1, true)); diff --git a/tests/forge/MathTest.t.sol b/tests/forge/MathTest.t.sol index e59e894d9..51291ba55 100644 --- a/tests/forge/MathTest.t.sol +++ b/tests/forge/MathTest.t.sol @@ -21,7 +21,6 @@ contract MathTest is DSTestPlus { function testZeroStaysZero() external { assertEq(Maths.rayToWad(0), 0); - assertEq(Maths.wadToRay(0), 0); } function testMultiplication() external { @@ -31,31 +30,6 @@ contract MathTest is DSTestPlus { assertEq(debt * inflator, 10_213.6546200311111065616975993 * 1e45); } - function testDivision() external { - uint256 debt = 11_000.143012091382543917 * 1e18; - uint256 price = 1_001.6501589292607751220 * 1e18; - - assertEq(Maths.wdiv(debt, price), 10.98202093218880245 * 1e18); - assertEq(debt * 1e18 / price, 10.98202093218880245 * 1e18); - assertEq(Maths.wwdivr(debt, price), 10.982020932188802450191601163 * 1e27); - - uint256 exchangeRate = 1.09232010 * 1e27; - assertEq(Maths.rdiv(Maths.wadToRay(debt), exchangeRate), Maths.wrdivr(debt, exchangeRate)); - - uint256 lpBalance = 36_900.58124 * 1e27; - uint256 lpRedemption = Maths.rdiv(lpBalance, exchangeRate); - assertEq(Maths.rayToWad(lpRedemption), Maths.rrdivw(lpBalance, exchangeRate)); - assertEq(Maths.rayToWad(Maths.rdiv(lpRedemption, Maths.wadToRay(price))), Maths.rwdivw(lpRedemption, price)); - - uint256 claimableCollateral1 = Maths.rwdivw(Maths.rdiv(lpBalance, exchangeRate), price); // rounds - uint256 claimableCollateral2 = lpBalance * 1e36 / exchangeRate / price; // truncates - assertEq(claimableCollateral1, 33.726184963566645999 * 1e18); - assertEq(claimableCollateral2, 33.726184963566645998 * 1e18); - - assertEq(Maths.wdiv(1 * 1e18, 60 * 1e18), 0.016666666666666667 * 1e18); - assertEq(Maths.rdiv(1 * 1e27, 3 * 1e27), 0.333333333333333333333333333 * 1e27); - } - function testScaleConversions() external { assertEq(Maths.wad(153), 153 * 1e18); } diff --git a/tests/forge/PositionManager.t.sol b/tests/forge/PositionManager.t.sol index 5e691fc0f..5545bf3bf 100644 --- a/tests/forge/PositionManager.t.sol +++ b/tests/forge/PositionManager.t.sol @@ -27,6 +27,7 @@ abstract contract PositionManagerERC20PoolHelperContract is ERC20HelperContract vm.prank(operator_); _quote.approve(address(_pool), type(uint256).max); + vm.prank(operator_); _quote.approve(address(_positionManager), type(uint256).max); } @@ -42,7 +43,7 @@ abstract contract PositionManagerERC20PoolHelperContract is ERC20HelperContract } function _getPermitSig( - address receiver_, + address spender_, uint256 tokenId_, uint256 deadline_, uint256 ownerPrivateKey_ @@ -56,7 +57,7 @@ abstract contract PositionManagerERC20PoolHelperContract is ERC20HelperContract keccak256( abi.encode( _positionManager.PERMIT_TYPEHASH(), - receiver_, + spender_, tokenId_, 0, deadline_ @@ -90,11 +91,11 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract require(tokenId != 0, "tokenId nonce not incremented"); // check position info - address owner = _positionManager.ownerOf(tokenId); - uint256 lpTokens = _positionManager.getLPTokens(tokenId, mintPrice); + address owner = _positionManager.ownerOf(tokenId); + uint256 lps = _positionManager.getLPs(tokenId, mintPrice); assertEq(owner, testAddress); - assertEq(lpTokens, 0); + assertEq(lps, 0); // deploy a new factory to simulate creating a pool outside of expected factories ERC20PoolFactory invalidFactory = new ERC20PoolFactory(_ajna); @@ -107,10 +108,10 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract /** * @notice Tests attachment of a created position to an already existing NFT. - * LP tokens are checked to verify ownership of position. + * LPs are checked to verify ownership of position. * Reverts: - * Attempts to memorialize when lp tokens aren't allowed to be transfered. - * Attempts to set position owner when not owner of the LP tokens. + * Attempts to memorialize when lps aren't allowed to be transfered. + * Attempts to set position owner when not owner of the LPs. */ function testMemorializePositions() external { address testAddress = makeAddr("testAddress"); @@ -124,27 +125,21 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract indexes[1] = 2551; indexes[2] = 2552; - _addInitialLiquidity( - { - from: testAddress, - amount: 3_000 * 1e18, - index: indexes[0] - } - ); - _addInitialLiquidity( - { - from: testAddress, - amount: 3_000 * 1e18, - index: indexes[1] - } - ); - _addInitialLiquidity( - { - from: testAddress, - amount: 3_000 * 1e18, - index: indexes[2] - } - ); + _addInitialLiquidity({ + from: testAddress, + amount: 3_000 * 1e18, + index: indexes[0] + }); + _addInitialLiquidity({ + from: testAddress, + amount: 3_000 * 1e18, + index: indexes[1] + }); + _addInitialLiquidity({ + from: testAddress, + amount: 3_000 * 1e18, + index: indexes[2] + }); // mint an NFT to later memorialize existing positions into uint256 tokenId = _mintNFT(testAddress, testAddress, address(_pool)); @@ -162,24 +157,24 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract _positionManager.memorializePositions(memorializeParams); // allow position manager to take ownership of the position - _pool.approveLpOwnership(address(_positionManager), indexes[0], 3_000 * 1e27); - _pool.approveLpOwnership(address(_positionManager), indexes[1], 3_000 * 1e27); - _pool.approveLpOwnership(address(_positionManager), indexes[2], 3_000 * 1e27); + _pool.approveLpOwnership(address(_positionManager), indexes[0], 3_000 * 1e18); + _pool.approveLpOwnership(address(_positionManager), indexes[1], 3_000 * 1e18); + _pool.approveLpOwnership(address(_positionManager), indexes[2], 3_000 * 1e18); // memorialize quote tokens into minted NFT vm.expectEmit(true, true, true, true); emit MemorializePosition(testAddress, tokenId); vm.expectEmit(true, true, true, true); - emit TransferLPTokens(testAddress, address(_positionManager), indexes, 9_000 * 1e27); + emit TransferLPs(testAddress, address(_positionManager), indexes, 9_000 * 1e18); _positionManager.memorializePositions(memorializeParams); // check memorialization success - uint256 positionAtPriceOneLPTokens = _positionManager.getLPTokens(tokenId, indexes[0]); - assertGt(positionAtPriceOneLPTokens, 0); + uint256 positionAtPriceOneLPs = _positionManager.getLPs(tokenId, indexes[0]); + assertGt(positionAtPriceOneLPs, 0); - // check lp tokens at non added to price - uint256 positionAtWrongPriceLPTokens = _positionManager.getLPTokens(tokenId, 4000000 * 1e18); - assertEq(positionAtWrongPriceLPTokens, 0); + // check lps at non added to price + uint256 positionAtWrongPriceLPs = _positionManager.getLPs(tokenId, 4000000 * 1e18); + assertEq(positionAtWrongPriceLPs, 0); assertTrue(_positionManager.isIndexInPosition(tokenId, 2550)); assertTrue(_positionManager.isIndexInPosition(tokenId, 2551)); @@ -198,85 +193,67 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract indexes[1] = 2551; indexes[2] = 2552; - _addInitialLiquidity( - { - from: testAddress, - amount: 3_000 * 1e18, - index: indexes[0] - } - ); - _addInitialLiquidity( - { - from: testAddress, - amount: 3_000 * 1e18, - index: indexes[1] - } - ); - _addInitialLiquidity( - { - from: testAddress, - amount: 3_000 * 1e18, - index: indexes[2] - } - ); + _addInitialLiquidity({ + from: testAddress, + amount: 3_000 * 1e18, + index: indexes[0] + }); + _addInitialLiquidity({ + from: testAddress, + amount: 3_000 * 1e18, + index: indexes[1] + }); + _addInitialLiquidity({ + from: testAddress, + amount: 3_000 * 1e18, + index: indexes[2] + }); // mint an NFT to later memorialize existing positions into uint256 tokenId = _mintNFT(testAddress, testAddress, address(_pool)); // check LPs - _assertLenderLpBalance( - { - lender: testAddress, - index: indexes[0], - lpBalance: 3_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[0], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: testAddress, - index: indexes[1], - lpBalance: 3_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[1], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: testAddress, - index: indexes[2], - lpBalance: 3_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[2], - lpBalance: 0, - depositTime: 0 - } - ); + _assertLenderLpBalance({ + lender: testAddress, + index: indexes[0], + lpBalance: 3_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[0], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: testAddress, + index: indexes[1], + lpBalance: 3_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[1], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: testAddress, + index: indexes[2], + lpBalance: 3_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[2], + lpBalance: 0, + depositTime: 0 + }); // check position manager state - assertEq(_positionManager.getLPTokens(tokenId, indexes[0]), 0); - assertEq(_positionManager.getLPTokens(tokenId, indexes[1]), 0); - assertEq(_positionManager.getLPTokens(tokenId, indexes[2]), 0); + assertEq(_positionManager.getLPs(tokenId, indexes[0]), 0); + assertEq(_positionManager.getLPs(tokenId, indexes[1]), 0); + assertEq(_positionManager.getLPs(tokenId, indexes[2]), 0); assertFalse(_positionManager.isIndexInPosition(tokenId, indexes[0])); assertFalse(_positionManager.isIndexInPosition(tokenId, indexes[1])); assertFalse(_positionManager.isIndexInPosition(tokenId, indexes[2])); @@ -286,221 +263,179 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract tokenId, indexes ); // allow position manager to take ownership of the position - _pool.approveLpOwnership(address(_positionManager), indexes[0], 3_000 * 1e27); - _pool.approveLpOwnership(address(_positionManager), indexes[1], 3_000 * 1e27); - _pool.approveLpOwnership(address(_positionManager), indexes[2], 3_000 * 1e27); + _pool.approveLpOwnership(address(_positionManager), indexes[0], 3_000 * 1e18); + _pool.approveLpOwnership(address(_positionManager), indexes[1], 3_000 * 1e18); + _pool.approveLpOwnership(address(_positionManager), indexes[2], 3_000 * 1e18); // memorialize quote tokens into minted NFT vm.expectEmit(true, true, true, true); emit MemorializePosition(testAddress, tokenId); vm.expectEmit(true, true, true, true); - emit TransferLPTokens(testAddress, address(_positionManager), indexes, 9_000 * 1e27); + emit TransferLPs(testAddress, address(_positionManager), indexes, 9_000 * 1e18); _positionManager.memorializePositions(memorializeParams); - _assertLenderLpBalance( - { - lender: testAddress, - index: indexes[0], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[0], - lpBalance: 3_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: testAddress, - index: indexes[1], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[1], - lpBalance: 3_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: testAddress, - index: indexes[2], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[2], - lpBalance: 3_000 * 1e27, - depositTime: _startTime - } - ); + _assertLenderLpBalance({ + lender: testAddress, + index: indexes[0], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[0], + lpBalance: 3_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: testAddress, + index: indexes[1], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[1], + lpBalance: 3_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: testAddress, + index: indexes[2], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[2], + lpBalance: 3_000 * 1e18, + depositTime: _startTime + }); // check position manager state - assertEq(_positionManager.getLPTokens(tokenId, indexes[0]), 3_000 * 1e27); - assertEq(_positionManager.getLPTokens(tokenId, indexes[1]), 3_000 * 1e27); - assertEq(_positionManager.getLPTokens(tokenId, indexes[2]), 3_000 * 1e27); + assertEq(_positionManager.getLPs(tokenId, indexes[0]), 3_000 * 1e18); + assertEq(_positionManager.getLPs(tokenId, indexes[1]), 3_000 * 1e18); + assertEq(_positionManager.getLPs(tokenId, indexes[2]), 3_000 * 1e18); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[0])); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[1])); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[2])); // add more liquidity - _addInitialLiquidity( - { - from: testAddress, - amount: 1_000 * 1e18, - index: indexes[0] - } - ); - _addInitialLiquidity( - { - from: testAddress, - amount: 2_000 * 1e18, - index: indexes[1] - } - ); - _addInitialLiquidity( - { - from: testAddress, - amount: 3_000 * 1e18, - index: indexes[2] - } - ); + _addInitialLiquidity({ + from: testAddress, + amount: 1_000 * 1e18, + index: indexes[0] + }); + _addInitialLiquidity({ + from: testAddress, + amount: 2_000 * 1e18, + index: indexes[1] + }); + _addInitialLiquidity({ + from: testAddress, + amount: 3_000 * 1e18, + index: indexes[2] + }); // check LP balance - _assertLenderLpBalance( - { - lender: testAddress, - index: indexes[0], - lpBalance: 1_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[0], - lpBalance: 3_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: testAddress, - index: indexes[1], - lpBalance: 2_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[1], - lpBalance: 3_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: testAddress, - index: indexes[2], - lpBalance: 3_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[2], - lpBalance: 3_000 * 1e27, - depositTime: _startTime - } - ); + _assertLenderLpBalance({ + lender: testAddress, + index: indexes[0], + lpBalance: 1_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[0], + lpBalance: 3_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: testAddress, + index: indexes[1], + lpBalance: 2_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[1], + lpBalance: 3_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: testAddress, + index: indexes[2], + lpBalance: 3_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[2], + lpBalance: 3_000 * 1e18, + depositTime: _startTime + }); // check position manager state - assertEq(_positionManager.getLPTokens(tokenId, indexes[0]), 3_000 * 1e27); - assertEq(_positionManager.getLPTokens(tokenId, indexes[1]), 3_000 * 1e27); - assertEq(_positionManager.getLPTokens(tokenId, indexes[2]), 3_000 * 1e27); + assertEq(_positionManager.getLPs(tokenId, indexes[0]), 3_000 * 1e18); + assertEq(_positionManager.getLPs(tokenId, indexes[1]), 3_000 * 1e18); + assertEq(_positionManager.getLPs(tokenId, indexes[2]), 3_000 * 1e18); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[0])); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[1])); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[2])); // allow position manager to take ownership of the new LPs - _pool.approveLpOwnership(address(_positionManager), indexes[0], 1_000 * 1e27); - _pool.approveLpOwnership(address(_positionManager), indexes[1], 2_000 * 1e27); - _pool.approveLpOwnership(address(_positionManager), indexes[2], 3_000 * 1e27); + _pool.approveLpOwnership(address(_positionManager), indexes[0], 1_000 * 1e18); + _pool.approveLpOwnership(address(_positionManager), indexes[1], 2_000 * 1e18); + _pool.approveLpOwnership(address(_positionManager), indexes[2], 3_000 * 1e18); // rememorialize quote tokens into minted NFT vm.expectEmit(true, true, true, true); emit MemorializePosition(testAddress, tokenId); vm.expectEmit(true, true, true, true); - emit TransferLPTokens(testAddress, address(_positionManager), indexes, 6_000 * 1e27); + emit TransferLPs(testAddress, address(_positionManager), indexes, 6_000 * 1e18); _positionManager.memorializePositions(memorializeParams); // check LP balance - _assertLenderLpBalance( - { - lender: testAddress, - index: indexes[0], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[0], - lpBalance: 4_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: testAddress, - index: indexes[1], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[1], - lpBalance: 5_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: testAddress, - index: indexes[2], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[2], - lpBalance: 6_000 * 1e27, - depositTime: _startTime - } - ); + _assertLenderLpBalance({ + lender: testAddress, + index: indexes[0], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[0], + lpBalance: 4_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: testAddress, + index: indexes[1], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[1], + lpBalance: 5_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: testAddress, + index: indexes[2], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[2], + lpBalance: 6_000 * 1e18, + depositTime: _startTime + }); // check position manager state - assertEq(_positionManager.getLPTokens(tokenId, indexes[0]), 4_000 * 1e27); - assertEq(_positionManager.getLPTokens(tokenId, indexes[1]), 5_000 * 1e27); - assertEq(_positionManager.getLPTokens(tokenId, indexes[2]), 6_000 * 1e27); + assertEq(_positionManager.getLPs(tokenId, indexes[0]), 4_000 * 1e18); + assertEq(_positionManager.getLPs(tokenId, indexes[1]), 5_000 * 1e18); + assertEq(_positionManager.getLPs(tokenId, indexes[2]), 6_000 * 1e18); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[0])); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[1])); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[2])); @@ -525,151 +460,117 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract indexes[2] = 2552; indexes[3] = 2553; - _addInitialLiquidity( - { - from: testLender1, - amount: 3_000 * 1e18, - index: indexes[0] - } - ); - _addInitialLiquidity( - { - from: testLender1, - amount: 3_000 * 1e18, - index: indexes[1] - } - ); - _addInitialLiquidity( - { - from: testLender1, - amount: 3_000 * 1e18, - index: indexes[2] - } - ); - - _addInitialLiquidity( - { - from: testLender2, - amount: 3_000 * 1e18, - index: indexes[0] - } - ); - _addInitialLiquidity( - { - from: testLender2, - amount: 3_000 * 1e18, - index: indexes[3] - } - ); + _addInitialLiquidity({ + from: testLender1, + amount: 3_000 * 1e18, + index: indexes[0] + }); + _addInitialLiquidity({ + from: testLender1, + amount: 3_000 * 1e18, + index: indexes[1] + }); + _addInitialLiquidity({ + from: testLender1, + amount: 3_000 * 1e18, + index: indexes[2] + }); + + _addInitialLiquidity({ + from: testLender2, + amount: 3_000 * 1e18, + index: indexes[0] + }); + _addInitialLiquidity({ + from: testLender2, + amount: 3_000 * 1e18, + index: indexes[3] + }); // mint NFTs to later memorialize existing positions into uint256 tokenId1 = _mintNFT(testLender1, testLender1, address(_pool)); uint256 tokenId2 = _mintNFT(testLender2, testLender2, address(_pool)); // check LPs - _assertLenderLpBalance( - { - lender: testLender1, - index: indexes[0], - lpBalance: 3_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: testLender2, - index: indexes[0], - lpBalance: 3_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[0], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: testLender1, - index: indexes[1], - lpBalance: 3_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: testLender2, - index: indexes[1], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[1], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: testLender1, - index: indexes[2], - lpBalance: 3_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: testLender2, - index: indexes[2], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[2], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: testLender1, - index: indexes[3], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: testLender2, - index: indexes[3], - lpBalance: 3_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[3], - lpBalance: 0, - depositTime: 0 - } - ); - - assertEq(_positionManager.getLPTokens(indexes[0], tokenId1), 0); - assertEq(_positionManager.getLPTokens(indexes[1], tokenId1), 0); - assertEq(_positionManager.getLPTokens(indexes[2], tokenId1), 0); - - assertEq(_positionManager.getLPTokens(indexes[0], tokenId2), 0); - assertEq(_positionManager.getLPTokens(indexes[3], tokenId2), 0); + _assertLenderLpBalance({ + lender: testLender1, + index: indexes[0], + lpBalance: 3_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: testLender2, + index: indexes[0], + lpBalance: 3_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[0], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: testLender1, + index: indexes[1], + lpBalance: 3_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: testLender2, + index: indexes[1], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[1], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: testLender1, + index: indexes[2], + lpBalance: 3_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: testLender2, + index: indexes[2], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[2], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: testLender1, + index: indexes[3], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: testLender2, + index: indexes[3], + lpBalance: 3_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[3], + lpBalance: 0, + depositTime: 0 + }); + + assertEq(_positionManager.getLPs(indexes[0], tokenId1), 0); + assertEq(_positionManager.getLPs(indexes[1], tokenId1), 0); + assertEq(_positionManager.getLPs(indexes[2], tokenId1), 0); + + assertEq(_positionManager.getLPs(indexes[0], tokenId2), 0); + assertEq(_positionManager.getLPs(indexes[3], tokenId2), 0); (uint256 poolSize, , , , ) = _poolUtils.poolLoansInfo(address(_pool)); assertEq(poolSize, 15_000 * 1e18); @@ -686,94 +587,78 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract // allow position manager to take ownership of lender 1's position changePrank(testLender1); - _pool.approveLpOwnership(address(_positionManager), indexes[0], 3_000 * 1e27); - _pool.approveLpOwnership(address(_positionManager), indexes[1], 3_000 * 1e27); - _pool.approveLpOwnership(address(_positionManager), indexes[2], 3_000 * 1e27); + _pool.approveLpOwnership(address(_positionManager), indexes[0], 3_000 * 1e18); + _pool.approveLpOwnership(address(_positionManager), indexes[1], 3_000 * 1e18); + _pool.approveLpOwnership(address(_positionManager), indexes[2], 3_000 * 1e18); // memorialize lender 1 quote tokens into minted NFT vm.expectEmit(true, true, true, true); emit MemorializePosition(testLender1, tokenId1); vm.expectEmit(true, true, true, true); - emit TransferLPTokens(testLender1, address(_positionManager), lender1Indexes, 9_000 * 1e27); + emit TransferLPs(testLender1, address(_positionManager), lender1Indexes, 9_000 * 1e18); _positionManager.memorializePositions(memorializeParams); // check lender, position manager, and pool state - _assertLenderLpBalance( - { - lender: testLender1, - index: indexes[0], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[0], - lpBalance: 3_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: testLender1, - index: indexes[1], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[1], - lpBalance: 3_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: testLender1, - index: indexes[2], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[2], - lpBalance: 3_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: testLender1, - index: indexes[3], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[3], - lpBalance: 0, - depositTime: 0 - } - ); - - assertEq(_positionManager.getLPTokens(tokenId1, indexes[0]), 3_000 * 1e27); - assertEq(_positionManager.getLPTokens(tokenId1, indexes[1]), 3_000 * 1e27); - assertEq(_positionManager.getLPTokens(tokenId1, indexes[2]), 3_000 * 1e27); + _assertLenderLpBalance({ + lender: testLender1, + index: indexes[0], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[0], + lpBalance: 3_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: testLender1, + index: indexes[1], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[1], + lpBalance: 3_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: testLender1, + index: indexes[2], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[2], + lpBalance: 3_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: testLender1, + index: indexes[3], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[3], + lpBalance: 0, + depositTime: 0 + }); + + assertEq(_positionManager.getLPs(tokenId1, indexes[0]), 3_000 * 1e18); + assertEq(_positionManager.getLPs(tokenId1, indexes[1]), 3_000 * 1e18); + assertEq(_positionManager.getLPs(tokenId1, indexes[2]), 3_000 * 1e18); (poolSize, , , , ) = _poolUtils.poolLoansInfo(address(_pool)); assertEq(poolSize, 15_000 * 1e18); // allow position manager to take ownership of lender 2's position changePrank(testLender2); - _pool.approveLpOwnership(address(_positionManager), indexes[0], 3_000 * 1e27); - _pool.approveLpOwnership(address(_positionManager), indexes[3], 3_000 * 1e27); + _pool.approveLpOwnership(address(_positionManager), indexes[0], 3_000 * 1e18); + _pool.approveLpOwnership(address(_positionManager), indexes[3], 3_000 * 1e18); // memorialize lender 2 quote tokens into minted NFT uint256[] memory newIndexes = new uint256[](2); @@ -787,81 +672,65 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract vm.expectEmit(true, true, true, true); emit MemorializePosition(testLender2, tokenId2); vm.expectEmit(true, true, true, true); - emit TransferLPTokens(testLender2, address(_positionManager), newIndexes, 6_000 * 1e27); + emit TransferLPs(testLender2, address(_positionManager), newIndexes, 6_000 * 1e18); _positionManager.memorializePositions(memorializeParams); // // check lender, position manager, and pool state - _assertLenderLpBalance( - { - lender: testLender2, - index: indexes[0], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[0], - lpBalance: 6_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: testLender2, - index: indexes[1], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[1], - lpBalance: 3_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: testLender2, - index: indexes[2], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[2], - lpBalance: 3_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: testLender2, - index: indexes[3], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[3], - lpBalance: 3_000 * 1e27, - depositTime: _startTime - } - ); - - assertEq(_positionManager.getLPTokens(tokenId1, indexes[0]), 3_000 * 1e27); - assertEq(_positionManager.getLPTokens(tokenId1, indexes[1]), 3_000 * 1e27); - assertEq(_positionManager.getLPTokens(tokenId1, indexes[2]), 3_000 * 1e27); - - assertEq(_positionManager.getLPTokens(tokenId2, indexes[0]), 3_000 * 1e27); - assertEq(_positionManager.getLPTokens(tokenId2, indexes[3]), 3_000 * 1e27); + _assertLenderLpBalance({ + lender: testLender2, + index: indexes[0], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[0], + lpBalance: 6_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: testLender2, + index: indexes[1], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[1], + lpBalance: 3_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: testLender2, + index: indexes[2], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[2], + lpBalance: 3_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: testLender2, + index: indexes[3], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[3], + lpBalance: 3_000 * 1e18, + depositTime: _startTime + }); + + assertEq(_positionManager.getLPs(tokenId1, indexes[0]), 3_000 * 1e18); + assertEq(_positionManager.getLPs(tokenId1, indexes[1]), 3_000 * 1e18); + assertEq(_positionManager.getLPs(tokenId1, indexes[2]), 3_000 * 1e18); + + assertEq(_positionManager.getLPs(tokenId2, indexes[0]), 3_000 * 1e18); + assertEq(_positionManager.getLPs(tokenId2, indexes[3]), 3_000 * 1e18); (poolSize, , , , ) = _poolUtils.poolLoansInfo(address(_pool)); assertEq(poolSize, 15_000 * 1e18); @@ -905,86 +774,73 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract // add initial liquidity uint256 mintAmount = 50_000 * 1e18; _mintQuoteAndApproveManagerTokens(testMinter, mintAmount); - _addInitialLiquidity( - { - from: testMinter, - amount: 15_000 * 1e18, - index: testIndexPrice - } - ); + + _addInitialLiquidity({ + from: testMinter, + amount: 15_000 * 1e18, + index: testIndexPrice + }); uint256 tokenId = _mintNFT(testMinter, testMinter, address(_pool)); // check owner assertEq(_positionManager.ownerOf(tokenId), testMinter); // check LPs - _assertLenderLpBalance( - { - lender: testMinter, - index: testIndexPrice, - lpBalance: 15_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: testReceiver, - index: testIndexPrice, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: testIndexPrice, - lpBalance: 0, - depositTime: 0 - } - ); + _assertLenderLpBalance({ + lender: testMinter, + index: testIndexPrice, + lpBalance: 15_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: testReceiver, + index: testIndexPrice, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: testIndexPrice, + lpBalance: 0, + depositTime: 0 + }); // check position manager state - assertEq(_positionManager.getLPTokens(tokenId, testIndexPrice), 0); + assertEq(_positionManager.getLPs(tokenId, testIndexPrice), 0); assertFalse(_positionManager.isIndexInPosition(tokenId, testIndexPrice)); // memorialize positions uint256[] memory indexes = new uint256[](1); indexes[0] = testIndexPrice; // allow position manager to take ownership of the position of testMinter - _pool.approveLpOwnership(address(_positionManager), indexes[0], 15_000 * 1e27); + _pool.approveLpOwnership(address(_positionManager), indexes[0], 15_000 * 1e18); // memorialize positions of testMinter IPositionManagerOwnerActions.MemorializePositionsParams memory memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams( tokenId, indexes ); _positionManager.memorializePositions(memorializeParams); - _assertLenderLpBalance( - { - lender: testMinter, - index: testIndexPrice, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: testReceiver, - index: testIndexPrice, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: testIndexPrice, - lpBalance: 15_000 * 1e27, - depositTime: _startTime - } - ); + _assertLenderLpBalance({ + lender: testMinter, + index: testIndexPrice, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: testReceiver, + index: testIndexPrice, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: testIndexPrice, + lpBalance: 15_000 * 1e18, + depositTime: _startTime + }); // check position manager state - assertEq(_positionManager.getLPTokens(tokenId, testIndexPrice), 15_000 * 1e27); + assertEq(_positionManager.getLPs(tokenId, testIndexPrice), 15_000 * 1e18); assertTrue(_positionManager.isIndexInPosition(tokenId, testIndexPrice)); // approve and transfer NFT to different address @@ -1008,33 +864,27 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract _positionManager.reedemPositions(reedemParams); // check pool state - _assertLenderLpBalance( - { - lender: testMinter, - index: testIndexPrice, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: testReceiver, - index: testIndexPrice, - lpBalance: 15_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: testIndexPrice, - lpBalance: 0, - depositTime: 0 - } - ); + _assertLenderLpBalance({ + lender: testMinter, + index: testIndexPrice, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: testReceiver, + index: testIndexPrice, + lpBalance: 15_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: testIndexPrice, + lpBalance: 0, + depositTime: 0 + }); // check position manager state - assertEq(_positionManager.getLPTokens(tokenId, testIndexPrice), 0); + assertEq(_positionManager.getLPs(tokenId, testIndexPrice), 0); assertFalse(_positionManager.isIndexInPosition(tokenId, testIndexPrice)); } @@ -1053,53 +903,46 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract // add initial liquidity uint256 mintAmount = 50_000 * 1e18; _mintQuoteAndApproveManagerTokens(testMinter, mintAmount); - _addInitialLiquidity( - { - from: testMinter, - amount: 15_000 * 1e18, - index: testIndexPrice - } - ); + + _addInitialLiquidity({ + from: testMinter, + amount: 15_000 * 1e18, + index: testIndexPrice + }); uint256 tokenId = _mintNFT(testMinter, testMinter, address(_pool)); // check owner assertEq(_positionManager.ownerOf(tokenId), testMinter); // check LPs - _assertLenderLpBalance( - { - lender: testMinter, - index: testIndexPrice, - lpBalance: 15_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: testReceiver, - index: testIndexPrice, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: testIndexPrice, - lpBalance: 0, - depositTime: 0 - } - ); + _assertLenderLpBalance({ + lender: testMinter, + index: testIndexPrice, + lpBalance: 15_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: testReceiver, + index: testIndexPrice, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: testIndexPrice, + lpBalance: 0, + depositTime: 0 + }); // check position manager state - assertEq(_positionManager.getLPTokens(tokenId, testIndexPrice), 0); + assertEq(_positionManager.getLPs(tokenId, testIndexPrice), 0); assertFalse(_positionManager.isIndexInPosition(tokenId, testIndexPrice)); // memorialize positions uint256[] memory indexes = new uint256[](1); indexes[0] = testIndexPrice; // allow position manager to take ownership of the position of testMinter - _pool.approveLpOwnership(address(_positionManager), indexes[0], 15_000 * 1e27); + _pool.approveLpOwnership(address(_positionManager), indexes[0], 15_000 * 1e18); // memorialize positions of testMinter IPositionManagerOwnerActions.MemorializePositionsParams memory memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams( tokenId, indexes @@ -1107,35 +950,31 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract _positionManager.memorializePositions(memorializeParams); // check pool state - _assertLenderLpBalance( - { - lender: testMinter, - index: testIndexPrice, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: testReceiver, - index: testIndexPrice, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: testIndexPrice, - lpBalance: 15_000 * 1e27, - depositTime: _startTime - } - ); + _assertLenderLpBalance({ + lender: testMinter, + index: testIndexPrice, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: testReceiver, + index: testIndexPrice, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: testIndexPrice, + lpBalance: 15_000 * 1e18, + depositTime: _startTime + }); // check position manager state - assertEq(_positionManager.getLPTokens(tokenId, testIndexPrice), 15_000 * 1e27); + assertEq(_positionManager.getLPs(tokenId, testIndexPrice), 15_000 * 1e18); assertTrue(_positionManager.isIndexInPosition(tokenId, testIndexPrice)); + address testSpender = makeAddr("testSpender"); + // approve and transfer NFT by permit to different address { uint256 deadline = block.timestamp + 1 days; @@ -1148,7 +987,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract keccak256( abi.encode( _positionManager.PERMIT_TYPEHASH(), - testReceiver, + testSpender, tokenId, 0, deadline @@ -1157,7 +996,8 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract ) ) ); - _positionManager.safeTransferFromWithPermit(testMinter, testReceiver, testReceiver, tokenId, deadline, v, r, s ); + changePrank(testSpender); + _positionManager.safeTransferFromWithPermit(testMinter, testReceiver, tokenId, deadline, v, r, s ); } // check owner @@ -1177,37 +1017,33 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract _positionManager.reedemPositions(reedemParams); // check pool state - _assertLenderLpBalance( - { - lender: testMinter, - index: testIndexPrice, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: testReceiver, - index: testIndexPrice, - lpBalance: 15_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: testIndexPrice, - lpBalance: 0, - depositTime: 0 - } - ); + _assertLenderLpBalance({ + lender: testMinter, + index: testIndexPrice, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: testReceiver, + index: testIndexPrice, + lpBalance: 15_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: testIndexPrice, + lpBalance: 0, + depositTime: 0 + }); // check position manager state - assertEq(_positionManager.getLPTokens(tokenId, testIndexPrice), 0); + assertEq(_positionManager.getLPs(tokenId, testIndexPrice), 0); assertFalse(_positionManager.isIndexInPosition(tokenId, testIndexPrice)); } function testPermitByContract() external { + address testSpender = makeAddr("spender"); + // deploy recipient contract (address nonMintingContractOwner, uint256 nonMintingContractPrivateKey) = makeAddrAndKey("nonMintingContract"); ContractNFTRecipient recipientContract = new ContractNFTRecipient(nonMintingContractOwner); @@ -1217,50 +1053,58 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract ContractNFTRecipient ownerContract = new ContractNFTRecipient(testContractOwner); uint256 tokenId = _mintNFT(address(ownerContract), address(ownerContract), address(_pool)); + changePrank(testSpender); + // check contract owned nft can't be signed by non owner uint256 deadline = block.timestamp + 1 days; - (uint8 v, bytes32 r, bytes32 s) = _getPermitSig(address(recipientContract), tokenId, deadline, nonMintingContractPrivateKey); + (uint8 v, bytes32 r, bytes32 s) = _getPermitSig(testSpender, tokenId, deadline, nonMintingContractPrivateKey); vm.expectRevert("ajna/nft-unauthorized"); - _positionManager.safeTransferFromWithPermit(address(ownerContract), address(recipientContract), address(recipientContract), tokenId, deadline, v, r, s ); + _positionManager.safeTransferFromWithPermit(address(ownerContract), address(recipientContract), tokenId, deadline, v, r, s ); // check owner can permit their contract to transfer the NFT deadline = block.timestamp + 1 days; - (v, r, s) = _getPermitSig(address(recipientContract), tokenId, deadline, ownerPrivateKey); - _positionManager.safeTransferFromWithPermit(address(ownerContract), address(recipientContract), address(recipientContract), tokenId, deadline, v, r, s ); + (v, r, s) = _getPermitSig(testSpender, tokenId, deadline, ownerPrivateKey); + _positionManager.safeTransferFromWithPermit(address(ownerContract), address(recipientContract), tokenId, deadline, v, r, s ); } function testPermitReverts() external { // generate addresses and set test params (address testMinter, uint256 minterPrivateKey) = makeAddrAndKey("testMinter"); (address testReceiver, uint256 receiverPrivateKey) = makeAddrAndKey("testReceiver"); + address testSpender = makeAddr("spender"); vm.prank(testMinter); uint256 tokenId = _mintNFT(testMinter, testMinter, address(_pool)); assertEq(_positionManager.ownerOf(tokenId), testMinter); + changePrank(testSpender); + // check can't use a deadline in the past uint256 deadline = block.timestamp - 1 days; - (uint8 v, bytes32 r, bytes32 s) = _getPermitSig(testReceiver, tokenId, deadline, minterPrivateKey); + (uint8 v, bytes32 r, bytes32 s) = _getPermitSig(testSpender, tokenId, deadline, minterPrivateKey); vm.expectRevert("ajna/nft-permit-expired"); - _positionManager.safeTransferFromWithPermit(testMinter, testReceiver, testReceiver, tokenId, deadline, v, r, s ); + _positionManager.safeTransferFromWithPermit(testMinter, testReceiver, tokenId, deadline, v, r, s ); // check can't self approve + changePrank(testMinter); deadline = block.timestamp + 1 days; - (v, r, s) = _getPermitSig(testMinter, tokenId, deadline, minterPrivateKey); + (v, r, s) = _getPermitSig(testSpender, tokenId, deadline, minterPrivateKey); vm.expectRevert("ERC721Permit: approval to current owner"); - _positionManager.safeTransferFromWithPermit(testMinter, testMinter, testMinter, tokenId, deadline, v, r, s ); + _positionManager.safeTransferFromWithPermit(testMinter, testMinter, tokenId, deadline, v, r, s ); + + changePrank(testSpender); // check signer is authorized to permit deadline = block.timestamp + 1 days; - (v, r, s) = _getPermitSig(testReceiver, tokenId, deadline, receiverPrivateKey); + (v, r, s) = _getPermitSig(testSpender, tokenId, deadline, receiverPrivateKey); vm.expectRevert("ajna/nft-unauthorized"); - _positionManager.safeTransferFromWithPermit(testMinter, testReceiver, testReceiver, tokenId, deadline, v, r, s ); + _positionManager.safeTransferFromWithPermit(testMinter, testReceiver, tokenId, deadline, v, r, s ); // check signature is valid deadline = block.timestamp + 1 days; - (v, r, s) = _getPermitSig(testReceiver, tokenId, deadline, minterPrivateKey); + (v, r, s) = _getPermitSig(testSpender, tokenId, deadline, minterPrivateKey); vm.expectRevert("ajna/nft-invalid-signature"); - _positionManager.safeTransferFromWithPermit(testMinter, testReceiver, testReceiver, tokenId, deadline, 0, r, s ); + _positionManager.safeTransferFromWithPermit(testMinter, testReceiver, tokenId, deadline, 0, r, s ); } /** @@ -1299,13 +1143,12 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract // add initial liquidity uint256 mintAmount = 50_000 * 1e18; _mintQuoteAndApproveManagerTokens(testMinter, mintAmount); - _addInitialLiquidity( - { - from: testMinter, - amount: 15_000 * 1e18, - index: testIndexPrice - } - ); + + _addInitialLiquidity({ + from: testMinter, + amount: 15_000 * 1e18, + index: testIndexPrice + }); uint256 tokenId = _mintNFT(testMinter, testMinter, address(_pool)); @@ -1316,7 +1159,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract uint256[] memory indexes = new uint256[](1); indexes[0] = testIndexPrice; // allow position manager to take ownership of the position of testMinter - _pool.approveLpOwnership(address(_positionManager), indexes[0], 15_000 * 1e27); + _pool.approveLpOwnership(address(_positionManager), indexes[0], 15_000 * 1e18); // memorialize positions of testMinter IPositionManagerOwnerActions.MemorializePositionsParams memory memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams( tokenId, indexes @@ -1353,20 +1196,18 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract address notOwner = makeAddr("notOwner"); _mintQuoteAndApproveManagerTokens(testAddress, 10_000 * 1e18); - _addInitialLiquidity( - { - from: testAddress, - amount: 10_000 * 1e18, - index: 2550 - } - ); + _addInitialLiquidity({ + from: testAddress, + amount: 10_000 * 1e18, + index: 2550 + }); // mint position NFT uint256 tokenId = _mintNFT(testAddress, testAddress, address(_pool)); // construct move liquidity params IPositionManagerOwnerActions.MoveLiquidityParams memory moveLiquidityParams = IPositionManagerOwnerActions.MoveLiquidityParams( - tokenId, address(_pool), 2550, 2551 + tokenId, address(_pool), 2550, 2551, block.timestamp + 30 ); // move liquidity should fail because is not performed by owner @@ -1386,20 +1227,16 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract _mintQuoteAndApproveManagerTokens(testAddress2, 10_000 * 1e18); _mintCollateralAndApproveTokens(testAddress3, 10_000 * 1e18); - _addInitialLiquidity( - { - from: testAddress1, - amount: 2_500 * 1e18, - index: mintIndex - } - ); - _addInitialLiquidity( - { - from: testAddress2, - amount: 5_500 * 1e18, - index: mintIndex - } - ); + _addInitialLiquidity({ + from: testAddress1, + amount: 2_500 * 1e18, + index: mintIndex + }); + _addInitialLiquidity({ + from: testAddress2, + amount: 5_500 * 1e18, + index: mintIndex + }); uint256 tokenId1 = _mintNFT(testAddress1, testAddress1, address(_pool)); uint256 tokenId2 = _mintNFT(testAddress2, testAddress2, address(_pool)); @@ -1407,60 +1244,48 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract assertEq(_positionManager.ownerOf(tokenId2), testAddress2); // check pool state - _assertLenderLpBalance( - { - lender: testAddress1, - index: mintIndex, - lpBalance: 2_500 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: testAddress2, - index: mintIndex, - lpBalance: 5_500 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: mintIndex, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: testAddress1, - index: moveIndex, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: testAddress2, - index: moveIndex, - lpBalance: 0 * 1e27, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: moveIndex, - lpBalance: 0, - depositTime: 0 - } - ); + _assertLenderLpBalance({ + lender: testAddress1, + index: mintIndex, + lpBalance: 2_500 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: testAddress2, + index: mintIndex, + lpBalance: 5_500 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: mintIndex, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: testAddress1, + index: moveIndex, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: testAddress2, + index: moveIndex, + lpBalance: 0 * 1e18, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: moveIndex, + lpBalance: 0, + depositTime: 0 + }); // check position manager state - assertEq(_positionManager.getLPTokens(tokenId1, mintIndex), 0); - assertEq(_positionManager.getLPTokens(tokenId1, moveIndex), 0); - assertEq(_positionManager.getLPTokens(tokenId2, mintIndex), 0); - assertEq(_positionManager.getLPTokens(tokenId2, moveIndex), 0); + assertEq(_positionManager.getLPs(tokenId1, mintIndex), 0); + assertEq(_positionManager.getLPs(tokenId1, moveIndex), 0); + assertEq(_positionManager.getLPs(tokenId2, mintIndex), 0); + assertEq(_positionManager.getLPs(tokenId2, moveIndex), 0); assertFalse(_positionManager.isIndexInPosition(tokenId1, mintIndex)); assertFalse(_positionManager.isIndexInPosition(tokenId1, moveIndex)); assertFalse(_positionManager.isIndexInPosition(tokenId2, mintIndex)); @@ -1468,7 +1293,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract // allow position manager to take ownership of the position of testAddress1 changePrank(testAddress1); - _pool.approveLpOwnership(address(_positionManager), mintIndex, 2_500 * 1e27); + _pool.approveLpOwnership(address(_positionManager), mintIndex, 2_500 * 1e18); // memorialize positions of testAddress1 uint256[] memory indexes = new uint256[](1); @@ -1480,60 +1305,48 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract _positionManager.memorializePositions(memorializeParams); // check pool state - _assertLenderLpBalance( - { - lender: testAddress1, - index: mintIndex, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: testAddress2, - index: mintIndex, - lpBalance: 5_500 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: mintIndex, - lpBalance: 2_500 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: testAddress1, - index: moveIndex, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: testAddress2, - index: moveIndex, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: moveIndex, - lpBalance: 0, - depositTime: 0 - } - ); + _assertLenderLpBalance({ + lender: testAddress1, + index: mintIndex, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: testAddress2, + index: mintIndex, + lpBalance: 5_500 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: mintIndex, + lpBalance: 2_500 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: testAddress1, + index: moveIndex, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: testAddress2, + index: moveIndex, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: moveIndex, + lpBalance: 0, + depositTime: 0 + }); // check position manager state - assertEq(_positionManager.getLPTokens(tokenId1, mintIndex), 2_500 * 1e27); - assertEq(_positionManager.getLPTokens(tokenId1, moveIndex), 0); - assertEq(_positionManager.getLPTokens(tokenId2, mintIndex), 0); - assertEq(_positionManager.getLPTokens(tokenId2, moveIndex), 0); + assertEq(_positionManager.getLPs(tokenId1, mintIndex), 2_500 * 1e18); + assertEq(_positionManager.getLPs(tokenId1, moveIndex), 0); + assertEq(_positionManager.getLPs(tokenId2, mintIndex), 0); + assertEq(_positionManager.getLPs(tokenId2, moveIndex), 0); assertTrue(_positionManager.isIndexInPosition(tokenId1, mintIndex)); assertFalse(_positionManager.isIndexInPosition(tokenId1, moveIndex)); assertFalse(_positionManager.isIndexInPosition(tokenId2, mintIndex)); @@ -1541,7 +1354,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract // construct move liquidity params IPositionManagerOwnerActions.MoveLiquidityParams memory moveLiquidityParams = IPositionManagerOwnerActions.MoveLiquidityParams( - tokenId1, address(_pool), mintIndex, moveIndex + tokenId1, address(_pool), mintIndex, moveIndex, block.timestamp + 30 ); // move liquidity called by testAddress1 owner @@ -1551,60 +1364,48 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract _positionManager.moveLiquidity(moveLiquidityParams); // check pool state - _assertLenderLpBalance( - { - lender: testAddress1, - index: mintIndex, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: testAddress2, - index: mintIndex, - lpBalance: 5_500 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: mintIndex, - lpBalance: 0, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: testAddress1, - index: moveIndex, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: testAddress2, - index: moveIndex, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: moveIndex, - lpBalance: 2_500 * 1e27, - depositTime: _startTime - } - ); + _assertLenderLpBalance({ + lender: testAddress1, + index: mintIndex, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: testAddress2, + index: mintIndex, + lpBalance: 5_500 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: mintIndex, + lpBalance: 0, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: testAddress1, + index: moveIndex, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: testAddress2, + index: moveIndex, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: moveIndex, + lpBalance: 2_500 * 1e18, + depositTime: _startTime + }); // check position manager state - assertEq(_positionManager.getLPTokens(tokenId1, mintIndex), 0); - assertEq(_positionManager.getLPTokens(tokenId1, moveIndex), 2_500 * 1e27); - assertEq(_positionManager.getLPTokens(tokenId2, mintIndex), 0); - assertEq(_positionManager.getLPTokens(tokenId2, moveIndex), 0); + assertEq(_positionManager.getLPs(tokenId1, mintIndex), 0); + assertEq(_positionManager.getLPs(tokenId1, moveIndex), 2_500 * 1e18); + assertEq(_positionManager.getLPs(tokenId2, mintIndex), 0); + assertEq(_positionManager.getLPs(tokenId2, moveIndex), 0); assertFalse(_positionManager.isIndexInPosition(tokenId1, mintIndex)); assertTrue(_positionManager.isIndexInPosition(tokenId1, moveIndex)); assertFalse(_positionManager.isIndexInPosition(tokenId2, mintIndex)); @@ -1612,7 +1413,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract // allow position manager to take ownership of the position of testAddress2 changePrank(testAddress2); - _pool.approveLpOwnership(address(_positionManager), mintIndex, 5_500 * 1e27); + _pool.approveLpOwnership(address(_positionManager), mintIndex, 5_500 * 1e18); // memorialize positions of testAddress2 memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams( @@ -1622,60 +1423,48 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract _positionManager.memorializePositions(memorializeParams); // check pool state - _assertLenderLpBalance( - { - lender: testAddress1, - index: mintIndex, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: testAddress2, - index: mintIndex, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: mintIndex, - lpBalance: 5_500 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: testAddress1, - index: moveIndex, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: testAddress2, - index: moveIndex, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: moveIndex, - lpBalance: 2_500 * 1e27, - depositTime: _startTime - } - ); + _assertLenderLpBalance({ + lender: testAddress1, + index: mintIndex, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: testAddress2, + index: mintIndex, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: mintIndex, + lpBalance: 5_500 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: testAddress1, + index: moveIndex, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: testAddress2, + index: moveIndex, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: moveIndex, + lpBalance: 2_500 * 1e18, + depositTime: _startTime + }); // check position manager state - assertEq(_positionManager.getLPTokens(tokenId1, mintIndex), 0); - assertEq(_positionManager.getLPTokens(tokenId1, moveIndex), 2_500 * 1e27); - assertEq(_positionManager.getLPTokens(tokenId2, mintIndex), 5_500 * 1e27); - assertEq(_positionManager.getLPTokens(tokenId2, moveIndex), 0); + assertEq(_positionManager.getLPs(tokenId1, mintIndex), 0); + assertEq(_positionManager.getLPs(tokenId1, moveIndex), 2_500 * 1e18); + assertEq(_positionManager.getLPs(tokenId2, mintIndex), 5_500 * 1e18); + assertEq(_positionManager.getLPs(tokenId2, moveIndex), 0); assertFalse(_positionManager.isIndexInPosition(tokenId1, mintIndex)); assertTrue(_positionManager.isIndexInPosition(tokenId1, moveIndex)); assertTrue(_positionManager.isIndexInPosition(tokenId2, mintIndex)); @@ -1683,17 +1472,15 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract // construct move liquidity params moveLiquidityParams = IPositionManagerOwnerActions.MoveLiquidityParams( - tokenId2, address(_pool), mintIndex, moveIndex + tokenId2, address(_pool), mintIndex, moveIndex, block.timestamp + 30 ); - _addCollateral( - { - from: testAddress3, - amount: 10_000 * 1e18, - index: mintIndex, - lpAward: 30_108_920.22197881557845 * 1e27 - } - ); + _addCollateral({ + from: testAddress3, + amount: 10_000 * 1e18, + index: mintIndex, + lpAward: 30_108_920.22197881557845 * 1e18 + }); // move liquidity called by testAddress2 owner vm.expectEmit(true, true, true, true); @@ -1702,60 +1489,48 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract _positionManager.moveLiquidity(moveLiquidityParams); // check pool state - _assertLenderLpBalance( - { - lender: testAddress1, - index: mintIndex, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: testAddress2, - index: mintIndex, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: mintIndex, - lpBalance: 0 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: testAddress1, - index: moveIndex, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: testAddress2, - index: moveIndex, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: moveIndex, - lpBalance: 8_000 * 1e27, - depositTime: _startTime - } - ); + _assertLenderLpBalance({ + lender: testAddress1, + index: mintIndex, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: testAddress2, + index: mintIndex, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: mintIndex, + lpBalance: 0 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: testAddress1, + index: moveIndex, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: testAddress2, + index: moveIndex, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: moveIndex, + lpBalance: 8_000 * 1e18, + depositTime: _startTime + }); // check position manager state - assertEq(_positionManager.getLPTokens(tokenId1, mintIndex), 0); - assertEq(_positionManager.getLPTokens(tokenId1, moveIndex), 2_500 * 1e27); - assertEq(_positionManager.getLPTokens(tokenId2, mintIndex), 0); - assertEq(_positionManager.getLPTokens(tokenId2, moveIndex), 5_500 * 1e27); + assertEq(_positionManager.getLPs(tokenId1, mintIndex), 0); + assertEq(_positionManager.getLPs(tokenId1, moveIndex), 2_500 * 1e18); + assertEq(_positionManager.getLPs(tokenId2, mintIndex), 0); + assertEq(_positionManager.getLPs(tokenId2, moveIndex), 5_500 * 1e18); assertFalse(_positionManager.isIndexInPosition(tokenId1, mintIndex)); assertTrue(_positionManager.isIndexInPosition(tokenId1, moveIndex)); assertFalse(_positionManager.isIndexInPosition(tokenId2, mintIndex)); @@ -1763,7 +1538,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract // check can't move liquidity from position with no liquidity moveLiquidityParams = IPositionManagerOwnerActions.MoveLiquidityParams( - tokenId2, address(_pool), 1000, 2000 + tokenId2, address(_pool), 1000, 2000, block.timestamp + 30 ); changePrank(address(testAddress2)); vm.expectRevert(IPositionManagerErrors.RemoveLiquidityFailed.selector); @@ -1778,45 +1553,40 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract // add initial liquidity uint256 mintAmount = 50_000 * 1e18; _mintQuoteAndApproveManagerTokens(testMinter, mintAmount); - _addInitialLiquidity( - { - from: testMinter, - amount: 15_000 * 1e18, - index: testIndexPrice - } - ); + + _addInitialLiquidity({ + from: testMinter, + amount: 15_000 * 1e18, + index: testIndexPrice + }); uint256 tokenId = _mintNFT(testMinter, testMinter, address(_pool)); // check owner assertEq(_positionManager.ownerOf(tokenId), testMinter); // check pool state - _assertLenderLpBalance( - { - lender: testMinter, - index: testIndexPrice, - lpBalance: 15_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: testIndexPrice, - lpBalance: 0, - depositTime: 0 - } - ); + _assertLenderLpBalance({ + lender: testMinter, + index: testIndexPrice, + lpBalance: 15_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: testIndexPrice, + lpBalance: 0, + depositTime: 0 + }); // check position manager state - assertEq(_positionManager.getLPTokens(tokenId, testIndexPrice), 0); + assertEq(_positionManager.getLPs(tokenId, testIndexPrice), 0); assertFalse(_positionManager.isIndexInPosition(tokenId, testIndexPrice)); // memorialize positions uint256[] memory indexes = new uint256[](1); indexes[0] = testIndexPrice; // allow position manager to take ownership of the position of testMinter - _pool.approveLpOwnership(address(_positionManager), indexes[0], 15_000 * 1e27); + _pool.approveLpOwnership(address(_positionManager), indexes[0], 15_000 * 1e18); // memorialize positions of testMinter IPositionManagerOwnerActions.MemorializePositionsParams memory memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams( tokenId, indexes @@ -1824,25 +1594,21 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract _positionManager.memorializePositions(memorializeParams); // check pool state - _assertLenderLpBalance( - { - lender: testMinter, - index: testIndexPrice, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: testIndexPrice, - lpBalance: 15_000 * 1e27, - depositTime: _startTime - } - ); + _assertLenderLpBalance({ + lender: testMinter, + index: testIndexPrice, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: testIndexPrice, + lpBalance: 15_000 * 1e18, + depositTime: _startTime + }); // check position manager state - assertEq(_positionManager.getLPTokens(tokenId, testIndexPrice), 15_000 * 1e27); + assertEq(_positionManager.getLPs(tokenId, testIndexPrice), 15_000 * 1e18); assertTrue(_positionManager.isIndexInPosition(tokenId, testIndexPrice)); // redeem positions of testMinter @@ -1862,25 +1628,21 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract _positionManager.reedemPositions(reedemParams); // check pool state - _assertLenderLpBalance( - { - lender: testMinter, - index: testIndexPrice, - lpBalance: 15_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: testIndexPrice, - lpBalance: 0, - depositTime: 0 - } - ); + _assertLenderLpBalance({ + lender: testMinter, + index: testIndexPrice, + lpBalance: 15_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: testIndexPrice, + lpBalance: 0, + depositTime: 0 + }); // check position manager state - assertEq(_positionManager.getLPTokens(tokenId, testIndexPrice), 0); + assertEq(_positionManager.getLPs(tokenId, testIndexPrice), 0); assertFalse(_positionManager.isIndexInPosition(tokenId, testIndexPrice)); // should fail if trying to redeem one more time @@ -1916,92 +1678,75 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract uint256 mintAmount = 50_000 * 1e18; _mintQuoteAndApproveManagerTokens(testMinter, mintAmount); _mintQuoteAndApproveManagerTokens(testReceiver, mintAmount); - _addInitialLiquidity( - { - from: testReceiver, - amount: 25_000 * 1e18, - index: testIndexPrice - } - ); - _addInitialLiquidity( - { - from: testReceiver, - amount: 15_000 * 1e18, - index: 2551 - } - ); - _addInitialLiquidity( - { - from: testMinter, - amount: 15_000 * 1e18, - index: testIndexPrice - } - ); + _addInitialLiquidity({ + from: testReceiver, + amount: 25_000 * 1e18, + index: testIndexPrice + }); + _addInitialLiquidity({ + from: testReceiver, + amount: 15_000 * 1e18, + index: 2551 + }); + + _addInitialLiquidity({ + from: testMinter, + amount: 15_000 * 1e18, + index: testIndexPrice + }); uint256 tokenId = _mintNFT(testMinter, testMinter, address(_pool)); // check owner assertEq(_positionManager.ownerOf(tokenId), testMinter); // check pool state - _assertLenderLpBalance( - { - lender: testMinter, - index: testIndexPrice, - lpBalance: 15_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: testReceiver, - index: testIndexPrice, - lpBalance: 25_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: testIndexPrice, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: testMinter, - index: 2551, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: testReceiver, - index: 2551, - lpBalance: 15_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: 2551, - lpBalance: 0, - depositTime: 0 - } - ); + _assertLenderLpBalance({ + lender: testMinter, + index: testIndexPrice, + lpBalance: 15_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: testReceiver, + index: testIndexPrice, + lpBalance: 25_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: testIndexPrice, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: testMinter, + index: 2551, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: testReceiver, + index: 2551, + lpBalance: 15_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: 2551, + lpBalance: 0, + depositTime: 0 + }); // check position manager state - assertEq(_positionManager.getLPTokens(tokenId, testIndexPrice), 0); + assertEq(_positionManager.getLPs(tokenId, testIndexPrice), 0); assertFalse(_positionManager.isIndexInPosition(tokenId, testIndexPrice)); // memorialize positions uint256[] memory indexes = new uint256[](1); indexes[0] = testIndexPrice; // allow position manager to take ownership of the position of testMinter - _pool.approveLpOwnership(address(_positionManager), indexes[0], 15_000 * 1e27); + _pool.approveLpOwnership(address(_positionManager), indexes[0], 15_000 * 1e18); // memorialize positions of testMinter IPositionManagerOwnerActions.MemorializePositionsParams memory memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams( tokenId, indexes @@ -2009,57 +1754,45 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract _positionManager.memorializePositions(memorializeParams); // check pool state - _assertLenderLpBalance( - { - lender: testMinter, - index: testIndexPrice, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: testReceiver, - index: testIndexPrice, - lpBalance: 25_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: testIndexPrice, - lpBalance: 15_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: testMinter, - index: 2551, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: testReceiver, - index: 2551, - lpBalance: 15_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: 2551, - lpBalance: 0, - depositTime: 0 - } - ); + _assertLenderLpBalance({ + lender: testMinter, + index: testIndexPrice, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: testReceiver, + index: testIndexPrice, + lpBalance: 25_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: testIndexPrice, + lpBalance: 15_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: testMinter, + index: 2551, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: testReceiver, + index: 2551, + lpBalance: 15_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: 2551, + lpBalance: 0, + depositTime: 0 + }); // check position manager state - assertEq(_positionManager.getLPTokens(tokenId, testIndexPrice), 15_000 * 1e27); + assertEq(_positionManager.getLPs(tokenId, testIndexPrice), 15_000 * 1e18); assertTrue(_positionManager.isIndexInPosition(tokenId, testIndexPrice)); // approve and transfer NFT to different address @@ -2089,62 +1822,50 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract vm.expectEmit(true, true, true, true); emit RedeemPosition(testReceiver, tokenId); vm.expectEmit(true, true, true, true); - emit TransferLPTokens(address(_positionManager), testReceiver, indexes, 15_000 * 1e27); + emit TransferLPs(address(_positionManager), testReceiver, indexes, 15_000 * 1e18); changePrank(testReceiver); _positionManager.reedemPositions(reedemParams); // check pool state - _assertLenderLpBalance( - { - lender: testMinter, - index: testIndexPrice, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: testReceiver, - index: testIndexPrice, - lpBalance: 40_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: testIndexPrice, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: testMinter, - index: 2551, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: testReceiver, - index: 2551, - lpBalance: 15_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: 2551, - lpBalance: 0, - depositTime: 0 - } - ); + _assertLenderLpBalance({ + lender: testMinter, + index: testIndexPrice, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: testReceiver, + index: testIndexPrice, + lpBalance: 40_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: testIndexPrice, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: testMinter, + index: 2551, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: testReceiver, + index: 2551, + lpBalance: 15_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: 2551, + lpBalance: 0, + depositTime: 0 + }); // check position manager state - assertEq(_positionManager.getLPTokens(tokenId, testIndexPrice), 0); + assertEq(_positionManager.getLPs(tokenId, testIndexPrice), 0); assertFalse(_positionManager.isIndexInPosition(tokenId, testIndexPrice)); } @@ -2159,39 +1880,32 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract uint256[] memory indexes = new uint256[](1); indexes[0] = 2550; - _addInitialLiquidity( - { - from: lender, - amount: 10_000 * 1e18, - index: 2550 - } - ); - _assertLenderLpBalance( - { - lender: lender, - index: 2550, - lpBalance: 10_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: minter, - index: 2550, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: 2550, - lpBalance: 0, - depositTime: 0 - } - ); + _addInitialLiquidity({ + from: lender, + amount: 10_000 * 1e18, + index: 2550 + }); + _assertLenderLpBalance({ + lender: lender, + index: 2550, + lpBalance: 10_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: minter, + index: 2550, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: 2550, + lpBalance: 0, + depositTime: 0 + }); + // allow position manager to take ownership of the position - _pool.approveLpOwnership(address(_positionManager), indexes[0], 10_000 * 1e27); + _pool.approveLpOwnership(address(_positionManager), indexes[0], 10_000 * 1e18); // 3rd party minter mints NFT and memorialize lender positions uint256 tokenId = _mintNFT(minter, lender, address(_pool)); @@ -2200,34 +1914,29 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract tokenId, indexes ); _positionManager.memorializePositions(memorializeParams); - _assertLenderLpBalance( - { - lender: lender, - index: 2550, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: minter, - index: 2550, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: 2550, - lpBalance: 10_000 * 1e27, - depositTime: _startTime - } - ); + + _assertLenderLpBalance({ + lender: lender, + index: 2550, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: minter, + index: 2550, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: 2550, + lpBalance: 10_000 * 1e18, + depositTime: _startTime + }); // minter cannot move liquidity on behalf of lender (is not approved) IPositionManagerOwnerActions.MoveLiquidityParams memory moveLiquidityParams = IPositionManagerOwnerActions.MoveLiquidityParams( - tokenId, address(_pool), 2550, 2551 + tokenId, address(_pool), 2550, 2551, block.timestamp + 30 ); vm.expectRevert(IPositionManagerErrors.NoAuth.selector); _positionManager.moveLiquidity(moveLiquidityParams); @@ -2253,30 +1962,25 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract changePrank(minter); // minter can move liquidity on behalf of lender _positionManager.moveLiquidity(moveLiquidityParams); - _assertLenderLpBalance( - { - lender: lender, - index: 2551, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: minter, - index: 2551, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: 2551, - lpBalance: 10_000 * 1e27, - depositTime: _startTime - } - ); + + _assertLenderLpBalance({ + lender: lender, + index: 2551, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: minter, + index: 2551, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: 2551, + lpBalance: 10_000 * 1e18, + depositTime: _startTime + }); // minter can redeem liquidity on behalf of lender indexes[0] = 2551; @@ -2284,30 +1988,25 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract tokenId, address(_pool), indexes ); _positionManager.reedemPositions(reedemParams); - _assertLenderLpBalance( - { - lender: lender, - index: 2551, - lpBalance: 10_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: minter, - index: 2551, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: 2551, - lpBalance: 0, - depositTime: 0 - } - ); + + _assertLenderLpBalance({ + lender: lender, + index: 2551, + lpBalance: 10_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: minter, + index: 2551, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: 2551, + lpBalance: 0, + depositTime: 0 + }); // minter can burn NFT on behalf of lender _positionManager.burn(burnParams); @@ -2326,31 +2025,26 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract uint256[] memory indexes = new uint256[](1); indexes[0] = 2550; - _addInitialLiquidity( - { - from: lender, - amount: 10_000 * 1e18, - index: 2550 - } - ); - _assertLenderLpBalance( - { - lender: lender, - index: 2550, - lpBalance: 10_000 * 1e27, - depositTime: _startTime - } - ); - _assertLenderLpBalance( - { - lender: minter, - index: 2550, - lpBalance: 0, - depositTime: 0 - } - ); + _addInitialLiquidity({ + from: lender, + amount: 10_000 * 1e18, + index: 2550 + }); + _assertLenderLpBalance({ + lender: lender, + index: 2550, + lpBalance: 10_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: minter, + index: 2550, + lpBalance: 0, + depositTime: 0 + }); + // allow position manager to take ownership of the position - _pool.approveLpOwnership(address(_positionManager), indexes[0], 10_000 * 1e27); + _pool.approveLpOwnership(address(_positionManager), indexes[0], 10_000 * 1e18); // 3rd party minter mints NFT and memorialize lender positions changePrank(minter); @@ -2372,22 +2066,19 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract tokenId, address(_pool), indexes ); _positionManager.reedemPositions(reedemParams); - _assertLenderLpBalance( - { - lender: lender, - index: 2550, - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: minter, - index: 2550, - lpBalance: 10_000 * 1e27, - depositTime: _startTime - } - ); + + _assertLenderLpBalance({ + lender: lender, + index: 2550, + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: minter, + index: 2550, + lpBalance: 10_000 * 1e18, + depositTime: _startTime + }); } function testMayInteractReverts() external { @@ -2434,13 +2125,11 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract uint256[] memory indexes = new uint256[](1); indexes[0] = 2550; - _addInitialLiquidity( - { - from: testAddress, - amount: 3_000 * 1e18, - index: indexes[0] - } - ); + _addInitialLiquidity({ + from: testAddress, + amount: 3_000 * 1e18, + index: indexes[0] + }); // mint NFT uint256 tokenId = _mintNFT(testAddress, testAddress, address(_pool)); @@ -2454,7 +2143,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract assertEq(tokenName(quoteTokenAddress), "Quote"); // allow position manager to take ownership of the position - _pool.approveLpOwnership(address(_positionManager), indexes[0], 3_000 * 1e27); + _pool.approveLpOwnership(address(_positionManager), indexes[0], 3_000 * 1e18); // memorialize position IPositionManagerOwnerActions.MemorializePositionsParams memory memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams( @@ -2514,85 +2203,67 @@ contract PositionManagerERC721PoolTest is PositionManagerERC721PoolHelperContrac indexes[1] = 2551; indexes[2] = 2552; - _addInitialLiquidity( - { - from: testAddress1, - amount: 3_000 * 1e18, - index: indexes[0] - } - ); - _addInitialLiquidity( - { - from: testAddress1, - amount: 3_000 * 1e18, - index: indexes[1] - } - ); - _addInitialLiquidity( - { - from: testAddress1, - amount: 3_000 * 1e18, - index: indexes[2] - } - ); + _addInitialLiquidity({ + from: testAddress1, + amount: 3_000 * 1e18, + index: indexes[0] + }); + _addInitialLiquidity({ + from: testAddress1, + amount: 3_000 * 1e18, + index: indexes[1] + }); + _addInitialLiquidity({ + from: testAddress1, + amount: 3_000 * 1e18, + index: indexes[2] + }); // mint an NFT to later memorialize existing positions into uint256 tokenId = _mintNFT(testAddress1, testAddress1, address(_pool), keccak256("ERC721_NON_SUBSET_HASH")); // check LPs - _assertLenderLpBalance( - { - lender: testAddress1, - index: indexes[0], - lpBalance: 3_000 * 1e27, - depositTime: currentTime - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[0], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: testAddress1, - index: indexes[1], - lpBalance: 3_000 * 1e27, - depositTime: currentTime - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[1], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: testAddress1, - index: indexes[2], - lpBalance: 3_000 * 1e27, - depositTime: currentTime - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[2], - lpBalance: 0, - depositTime: 0 - } - ); + _assertLenderLpBalance({ + lender: testAddress1, + index: indexes[0], + lpBalance: 3_000 * 1e18, + depositTime: currentTime + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[0], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: testAddress1, + index: indexes[1], + lpBalance: 3_000 * 1e18, + depositTime: currentTime + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[1], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: testAddress1, + index: indexes[2], + lpBalance: 3_000 * 1e18, + depositTime: currentTime + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[2], + lpBalance: 0, + depositTime: 0 + }); // check position manager state - assertEq(_positionManager.getLPTokens(tokenId, indexes[0]), 0); - assertEq(_positionManager.getLPTokens(tokenId, indexes[1]), 0); - assertEq(_positionManager.getLPTokens(tokenId, indexes[2]), 0); + assertEq(_positionManager.getLPs(tokenId, indexes[0]), 0); + assertEq(_positionManager.getLPs(tokenId, indexes[1]), 0); + assertEq(_positionManager.getLPs(tokenId, indexes[2]), 0); assertFalse(_positionManager.isIndexInPosition(tokenId, indexes[0])); assertFalse(_positionManager.isIndexInPosition(tokenId, indexes[1])); assertFalse(_positionManager.isIndexInPosition(tokenId, indexes[2])); @@ -2602,228 +2273,186 @@ contract PositionManagerERC721PoolTest is PositionManagerERC721PoolHelperContrac tokenId, indexes ); // allow position manager to take ownership of the position - _pool.approveLpOwnership(address(_positionManager), indexes[0], 3_000 * 1e27); - _pool.approveLpOwnership(address(_positionManager), indexes[1], 3_000 * 1e27); - _pool.approveLpOwnership(address(_positionManager), indexes[2], 3_000 * 1e27); + _pool.approveLpOwnership(address(_positionManager), indexes[0], 3_000 * 1e18); + _pool.approveLpOwnership(address(_positionManager), indexes[1], 3_000 * 1e18); + _pool.approveLpOwnership(address(_positionManager), indexes[2], 3_000 * 1e18); // memorialize quote tokens into minted NFT vm.expectEmit(true, true, true, true); emit MemorializePosition(testAddress1, tokenId); vm.expectEmit(true, true, true, true); - emit TransferLPTokens(testAddress1, address(_positionManager), indexes, 9_000 * 1e27); + emit TransferLPs(testAddress1, address(_positionManager), indexes, 9_000 * 1e18); _positionManager.memorializePositions(memorializeParams); - _assertLenderLpBalance( - { - lender: testAddress1, - index: indexes[0], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[0], - lpBalance: 3_000 * 1e27, - depositTime: currentTime - } - ); - _assertLenderLpBalance( - { - lender: testAddress1, - index: indexes[1], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[1], - lpBalance: 3_000 * 1e27, - depositTime: currentTime - } - ); - _assertLenderLpBalance( - { - lender: testAddress1, - index: indexes[2], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[2], - lpBalance: 3_000 * 1e27, - depositTime: currentTime - } - ); + _assertLenderLpBalance({ + lender: testAddress1, + index: indexes[0], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[0], + lpBalance: 3_000 * 1e18, + depositTime: currentTime + }); + _assertLenderLpBalance({ + lender: testAddress1, + index: indexes[1], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[1], + lpBalance: 3_000 * 1e18, + depositTime: currentTime + }); + _assertLenderLpBalance({ + lender: testAddress1, + index: indexes[2], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[2], + lpBalance: 3_000 * 1e18, + depositTime: currentTime + }); // check position manager state - assertEq(_positionManager.getLPTokens(tokenId, indexes[0]), 3_000 * 1e27); - assertEq(_positionManager.getLPTokens(tokenId, indexes[1]), 3_000 * 1e27); - assertEq(_positionManager.getLPTokens(tokenId, indexes[2]), 3_000 * 1e27); + assertEq(_positionManager.getLPs(tokenId, indexes[0]), 3_000 * 1e18); + assertEq(_positionManager.getLPs(tokenId, indexes[1]), 3_000 * 1e18); + assertEq(_positionManager.getLPs(tokenId, indexes[2]), 3_000 * 1e18); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[0])); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[1])); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[2])); // add more liquidity - _addInitialLiquidity( - { - from: testAddress1, - amount: 1_000 * 1e18, - index: indexes[0] - } - ); - _addInitialLiquidity( - { - from: testAddress1, - amount: 2_000 * 1e18, - index: indexes[1] - } - ); - _addInitialLiquidity( - { - from: testAddress1, - amount: 3_000 * 1e18, - index: indexes[2] - } - ); + _addInitialLiquidity({ + from: testAddress1, + amount: 1_000 * 1e18, + index: indexes[0] + }); + _addInitialLiquidity({ + from: testAddress1, + amount: 2_000 * 1e18, + index: indexes[1] + }); + _addInitialLiquidity({ + from: testAddress1, + amount: 3_000 * 1e18, + index: indexes[2] + }); // check LP balance - _assertLenderLpBalance( - { - lender: testAddress1, - index: indexes[0], - lpBalance: 1_000 * 1e27, - depositTime: currentTime - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[0], - lpBalance: 3_000 * 1e27, - depositTime: currentTime - } - ); - _assertLenderLpBalance( - { - lender: testAddress1, - index: indexes[1], - lpBalance: 2_000 * 1e27, - depositTime: currentTime - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[1], - lpBalance: 3_000 * 1e27, - depositTime: currentTime - } - ); - _assertLenderLpBalance( - { - lender: testAddress1, - index: indexes[2], - lpBalance: 3_000 * 1e27, - depositTime: currentTime - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[2], - lpBalance: 3_000 * 1e27, - depositTime: currentTime - } - ); + _assertLenderLpBalance({ + lender: testAddress1, + index: indexes[0], + lpBalance: 1_000 * 1e18, + depositTime: currentTime + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[0], + lpBalance: 3_000 * 1e18, + depositTime: currentTime + }); + _assertLenderLpBalance({ + lender: testAddress1, + index: indexes[1], + lpBalance: 2_000 * 1e18, + depositTime: currentTime + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[1], + lpBalance: 3_000 * 1e18, + depositTime: currentTime + }); + _assertLenderLpBalance({ + lender: testAddress1, + index: indexes[2], + lpBalance: 3_000 * 1e18, + depositTime: currentTime + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[2], + lpBalance: 3_000 * 1e18, + depositTime: currentTime + }); // check position manager state - assertEq(_positionManager.getLPTokens(tokenId, indexes[0]), 3_000 * 1e27); - assertEq(_positionManager.getLPTokens(tokenId, indexes[1]), 3_000 * 1e27); - assertEq(_positionManager.getLPTokens(tokenId, indexes[2]), 3_000 * 1e27); + assertEq(_positionManager.getLPs(tokenId, indexes[0]), 3_000 * 1e18); + assertEq(_positionManager.getLPs(tokenId, indexes[1]), 3_000 * 1e18); + assertEq(_positionManager.getLPs(tokenId, indexes[2]), 3_000 * 1e18); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[0])); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[1])); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[2])); // allow position manager to take ownership of the new LPs - _pool.approveLpOwnership(address(_positionManager), indexes[0], 1_000 * 1e27); - _pool.approveLpOwnership(address(_positionManager), indexes[1], 2_000 * 1e27); - _pool.approveLpOwnership(address(_positionManager), indexes[2], 3_000 * 1e27); + _pool.approveLpOwnership(address(_positionManager), indexes[0], 1_000 * 1e18); + _pool.approveLpOwnership(address(_positionManager), indexes[1], 2_000 * 1e18); + _pool.approveLpOwnership(address(_positionManager), indexes[2], 3_000 * 1e18); // rememorialize quote tokens into minted NFT vm.expectEmit(true, true, true, true); emit MemorializePosition(testAddress1, tokenId); vm.expectEmit(true, true, true, true); - emit TransferLPTokens(testAddress1, address(_positionManager), indexes, 6_000 * 1e27); + emit TransferLPs(testAddress1, address(_positionManager), indexes, 6_000 * 1e18); _positionManager.memorializePositions(memorializeParams); // check LP balance - _assertLenderLpBalance( - { - lender: testAddress1, - index: indexes[0], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[0], - lpBalance: 4_000 * 1e27, - depositTime: currentTime - } - ); - _assertLenderLpBalance( - { - lender: testAddress1, - index: indexes[1], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[1], - lpBalance: 5_000 * 1e27, - depositTime: currentTime - } - ); - _assertLenderLpBalance( - { - lender: testAddress1, - index: indexes[2], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[2], - lpBalance: 6_000 * 1e27, - depositTime: currentTime - } - ); + _assertLenderLpBalance({ + lender: testAddress1, + index: indexes[0], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[0], + lpBalance: 4_000 * 1e18, + depositTime: currentTime + }); + _assertLenderLpBalance({ + lender: testAddress1, + index: indexes[1], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[1], + lpBalance: 5_000 * 1e18, + depositTime: currentTime + }); + _assertLenderLpBalance({ + lender: testAddress1, + index: indexes[2], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[2], + lpBalance: 6_000 * 1e18, + depositTime: currentTime + }); // check position manager state - assertEq(_positionManager.getLPTokens(tokenId, indexes[0]), 4_000 * 1e27); - assertEq(_positionManager.getLPTokens(tokenId, indexes[1]), 5_000 * 1e27); - assertEq(_positionManager.getLPTokens(tokenId, indexes[2]), 6_000 * 1e27); + assertEq(_positionManager.getLPs(tokenId, indexes[0]), 4_000 * 1e18); + assertEq(_positionManager.getLPs(tokenId, indexes[1]), 5_000 * 1e18); + assertEq(_positionManager.getLPs(tokenId, indexes[2]), 6_000 * 1e18); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[0])); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[1])); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[2])); // construct move liquidity params IPositionManagerOwnerActions.MoveLiquidityParams memory moveLiquidityParams = IPositionManagerOwnerActions.MoveLiquidityParams( - tokenId, address(_pool), indexes[0], indexes[1] + tokenId, address(_pool), indexes[0], indexes[1], block.timestamp + 30 ); // move liquidity called by testAddress1 @@ -2833,59 +2462,47 @@ contract PositionManagerERC721PoolTest is PositionManagerERC721PoolHelperContrac _positionManager.moveLiquidity(moveLiquidityParams); // check LP balance - _assertLenderLpBalance( - { - lender: testAddress1, - index: indexes[0], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[0], - lpBalance: 0, - depositTime: currentTime - } - ); - _assertLenderLpBalance( - { - lender: testAddress1, - index: indexes[1], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[1], - lpBalance: 9_000 * 1e27, - depositTime: currentTime - } - ); - _assertLenderLpBalance( - { - lender: testAddress1, - index: indexes[2], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[2], - lpBalance: 6_000 * 1e27, - depositTime: currentTime - } - ); + _assertLenderLpBalance({ + lender: testAddress1, + index: indexes[0], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[0], + lpBalance: 0, + depositTime: currentTime + }); + _assertLenderLpBalance({ + lender: testAddress1, + index: indexes[1], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[1], + lpBalance: 9_000 * 1e18, + depositTime: currentTime + }); + _assertLenderLpBalance({ + lender: testAddress1, + index: indexes[2], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[2], + lpBalance: 6_000 * 1e18, + depositTime: currentTime + }); // check position manager state - assertEq(_positionManager.getLPTokens(tokenId, indexes[0]), 0); - assertEq(_positionManager.getLPTokens(tokenId, indexes[1]), 9_000 * 1e27); - assertEq(_positionManager.getLPTokens(tokenId, indexes[2]), 6_000 * 1e27); + assertEq(_positionManager.getLPs(tokenId, indexes[0]), 0); + assertEq(_positionManager.getLPs(tokenId, indexes[1]), 9_000 * 1e18); + assertEq(_positionManager.getLPs(tokenId, indexes[2]), 6_000 * 1e18); assertFalse(_positionManager.isIndexInPosition(tokenId, indexes[0])); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[1])); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[2])); @@ -2928,83 +2545,65 @@ contract PositionManagerERC721PoolTest is PositionManagerERC721PoolHelperContrac _positionManager.reedemPositions(reedemParams); // check pool state - _assertLenderLpBalance( - { - lender: testAddress1, - index: indexes[0], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: testAddress2, - index: indexes[0], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[0], - lpBalance: 0, - depositTime: currentTime - } - ); - _assertLenderLpBalance( - { - lender: testAddress1, - index: indexes[0], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: testAddress2, - index: indexes[1], - lpBalance: 9_000 * 1e27, - depositTime: currentTime - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[1], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: testAddress1, - index: indexes[0], - lpBalance: 0, - depositTime: 0 - } - ); - _assertLenderLpBalance( - { - lender: testAddress2, - index: indexes[2], - lpBalance: 6_000 * 1e27, - depositTime: currentTime - } - ); - _assertLenderLpBalance( - { - lender: address(_positionManager), - index: indexes[2], - lpBalance: 0, - depositTime: 0 - } - ); + _assertLenderLpBalance({ + lender: testAddress1, + index: indexes[0], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: testAddress2, + index: indexes[0], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[0], + lpBalance: 0, + depositTime: currentTime + }); + _assertLenderLpBalance({ + lender: testAddress1, + index: indexes[0], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: testAddress2, + index: indexes[1], + lpBalance: 9_000 * 1e18, + depositTime: currentTime + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[1], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: testAddress1, + index: indexes[0], + lpBalance: 0, + depositTime: 0 + }); + _assertLenderLpBalance({ + lender: testAddress2, + index: indexes[2], + lpBalance: 6_000 * 1e18, + depositTime: currentTime + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: indexes[2], + lpBalance: 0, + depositTime: 0 + }); // check position manager state - assertEq(_positionManager.getLPTokens(tokenId, indexes[0]), 0); - assertEq(_positionManager.getLPTokens(tokenId, indexes[1]), 0); - assertEq(_positionManager.getLPTokens(tokenId, indexes[2]), 0); + assertEq(_positionManager.getLPs(tokenId, indexes[0]), 0); + assertEq(_positionManager.getLPs(tokenId, indexes[1]), 0); + assertEq(_positionManager.getLPs(tokenId, indexes[2]), 0); assertFalse(_positionManager.isIndexInPosition(tokenId, indexes[0])); assertFalse(_positionManager.isIndexInPosition(tokenId, indexes[1])); assertFalse(_positionManager.isIndexInPosition(tokenId, indexes[2])); diff --git a/tests/forge/RewardsManager.t.sol b/tests/forge/RewardsManager.t.sol index cbfe9578a..2bad9d0b7 100644 --- a/tests/forge/RewardsManager.t.sol +++ b/tests/forge/RewardsManager.t.sol @@ -160,8 +160,15 @@ contract RewardsManagerTest is DSTestPlus { _rewardsManager.unstake(tokenId); assertEq(_positionManager.ownerOf(tokenId), minter); - // check token was transferred to rewards contract + // check token was transferred from rewards contract to minter assertEq(_positionManager.ownerOf(tokenId), address(minter)); + + // invariant: all bucket snapshots are removed for the token id that was unstaken + for(uint256 bucketIndex = 0; bucketIndex <= 7388; bucketIndex++) { + (uint256 lps, uint256 rate) = _rewardsManager.getBucketStateStakeInfo(tokenId, bucketIndex); + assertEq(lps, 0); + assertEq(rate, 0); + } } function _triggerReserveAuctionsNoTake(TriggerReserveAuctionParams memory params_) internal { @@ -179,7 +186,7 @@ contract RewardsManagerTest is DSTestPlus { // borrower repays some of their debt, providing reserves to be claimed // don't pull any collateral, as such functionality is unrelated to reserve auctions - params_.pool.repayDebt(borrower, Maths.wdiv(params_.borrowAmount, Maths.wad(2)), 0); + params_.pool.repayDebt(borrower, Maths.wdiv(params_.borrowAmount, Maths.wad(2)), 0, borrower, MAX_FENWICK_INDEX); // start reserve auction changePrank(_bidder); @@ -240,7 +247,7 @@ contract RewardsManagerTest is DSTestPlus { tokenId_ = _positionManager.mint(mintParams); for (uint256 i = 0; i < params_.indexes.length; i++) { - params_.pool.addQuoteToken(params_.mintAmount, params_.indexes[i]); + params_.pool.addQuoteToken(params_.mintAmount, params_.indexes[i], type(uint256).max); (uint256 lpBalance, ) = params_.pool.lenderInfo(params_.indexes[i], params_.minter); params_.pool.approveLpOwnership(address(_positionManager), params_.indexes[i], lpBalance); } @@ -279,7 +286,7 @@ contract RewardsManagerTest is DSTestPlus { // borrower repays some of their debt, providing reserves to be claimed // don't pull any collateral, as such functionality is unrelated to reserve auctions - params_.pool.repayDebt(borrower, params_.borrowAmount, 0); + params_.pool.repayDebt(borrower, params_.borrowAmount, 0, borrower, MAX_FENWICK_INDEX); // start reserve auction changePrank(_bidder); @@ -408,7 +415,7 @@ contract RewardsManagerTest is DSTestPlus { // check rewards earned uint256 rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, currentBurnEpoch); - assertEq(rewardsEarned, 40.214136545950568150 * 1e18); + assertEq(rewardsEarned, 40.214136545950568100 * 1e18); // claim rewards accrued since deposit changePrank(_minterOne); @@ -504,7 +511,7 @@ contract RewardsManagerTest is DSTestPlus { pool: address(_poolOne), tokenId: tokenIdOne, claimedArray: _epochsClaimedArray(2, 0), - reward: 80.793427892333608620 * 1e18, + reward: 80.793427892333608615 * 1e18, updateRatesReward: 3.689026486034825940 * 1e18 }); } @@ -623,7 +630,7 @@ contract RewardsManagerTest is DSTestPlus { // borrower1 repays their loan (uint256 debt, , ) = _poolOne.borrowerInfo(borrower1); - _poolOne.repayDebt(borrower1, debt, 0); + _poolOne.repayDebt(borrower1, debt, 0, borrower1, MAX_FENWICK_INDEX); /*****************************/ /*** First Reserve Auction ***/ @@ -683,7 +690,7 @@ contract RewardsManagerTest is DSTestPlus { // borrower1 repays their loan again changePrank(borrower1); (debt, , ) = _poolOne.borrowerInfo(borrower1); - _poolOne.repayDebt(borrower1, debt, 0); + _poolOne.repayDebt(borrower1, debt, 0, borrower1, MAX_FENWICK_INDEX); // recorder updates the change in exchange rates in the second index _updateExchangeRates({ @@ -704,7 +711,7 @@ contract RewardsManagerTest is DSTestPlus { pool: address(_poolOne), tokenId: tokenIdOne, claimedArray: _epochsClaimedArray(1, 0), - reward: 0.298393183929769729 * 1e18, + reward: 0.298393183929769450 * 1e18, updateRatesReward: 0 }); } @@ -759,7 +766,7 @@ contract RewardsManagerTest is DSTestPlus { assertEq(_ajnaToken.balanceOf(_updater), 18.914328218434904846 * 1e18); uint256 rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarned, 189.143282184349085719 * 1e18); + assertEq(rewardsEarned, 189.143282184349085696 * 1e18); assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); /******************************/ @@ -784,7 +791,7 @@ contract RewardsManagerTest is DSTestPlus { // check available rewards rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarned, 354.209322508542220912 * 1e18); + assertEq(rewardsEarned, 354.209322508542220207 * 1e18); assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); /*****************************/ @@ -801,7 +808,7 @@ contract RewardsManagerTest is DSTestPlus { // skip updating exchange rates and check available rewards uint256 rewardsEarnedNoUpdate = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarnedNoUpdate, 354.209322508542220912 * 1e18); + assertEq(rewardsEarnedNoUpdate, 354.209322508542220207 * 1e18); assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); // snapshot calling update exchange rate @@ -817,7 +824,7 @@ contract RewardsManagerTest is DSTestPlus { // check available rewards rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarned, 489.772410159936903182 * 1e18); + assertEq(rewardsEarned, 489.772410159936902605 * 1e18); assertGt(rewardsEarned, rewardsEarnedNoUpdate); assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); @@ -838,7 +845,7 @@ contract RewardsManagerTest is DSTestPlus { // check rewards earned rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarned, 354.209322508542220912 * 1e18); + assertEq(rewardsEarned, 354.209322508542220207 * 1e18); // call update exchange rate changePrank(_updater2); @@ -850,7 +857,7 @@ contract RewardsManagerTest is DSTestPlus { // check rewards earned won't increase since previous update was missed rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarned, 354.209322508542220912 * 1e18); + assertEq(rewardsEarned, 354.209322508542220207 * 1e18); /*****************************/ /*** Fifth Reserve Auction ***/ @@ -873,7 +880,7 @@ contract RewardsManagerTest is DSTestPlus { assertEq(_ajnaToken.balanceOf(_updater2), 10.978967849507124864 * 1e18); rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarned, 463.999001003613678404 * 1e18); + assertEq(rewardsEarned, 463.999001003613677587 * 1e18); // claim all rewards accrued since deposit changePrank(_minterOne); @@ -904,11 +911,11 @@ contract RewardsManagerTest is DSTestPlus { }); uint256 tokenIdTwo = _mintAndMemorializePositionNFT(mintMemorializeParams); // bucket exchange rates are not changed at the time minter two stakes - assertEq(_poolOne.bucketExchangeRate(2550), 1e27); - assertEq(_poolOne.bucketExchangeRate(2551), 1e27); - assertEq(_poolOne.bucketExchangeRate(2552), 1e27); - assertEq(_poolOne.bucketExchangeRate(2553), 1e27); - assertEq(_poolOne.bucketExchangeRate(2555), 1e27); + assertEq(_poolOne.bucketExchangeRate(2550), 1e18); + assertEq(_poolOne.bucketExchangeRate(2551), 1e18); + assertEq(_poolOne.bucketExchangeRate(2552), 1e18); + assertEq(_poolOne.bucketExchangeRate(2553), 1e18); + assertEq(_poolOne.bucketExchangeRate(2555), 1e18); _stakeToken(address(_poolOne), _minterTwo, tokenIdTwo); // borrower borrows and change the exchange rates of buckets @@ -928,11 +935,11 @@ contract RewardsManagerTest is DSTestPlus { }); uint256 tokenIdThree = _mintAndMemorializePositionNFT(mintMemorializeParams); // bucket exchange rates are higher at the time minter three stakes - assertEq(_poolOne.bucketExchangeRate(2550), 1.000000116565164638999999999 * 1e27); - assertEq(_poolOne.bucketExchangeRate(2551), 1.000000116565164638999999999 * 1e27); - assertEq(_poolOne.bucketExchangeRate(2552), 1.000000116565164638999999999 * 1e27); - assertEq(_poolOne.bucketExchangeRate(2553), 1.000000116565164638999999999 * 1e27); - assertEq(_poolOne.bucketExchangeRate(2555), 1.000000116565164638999999999 * 1e27); + assertEq(_poolOne.bucketExchangeRate(2550), 1.000000116565164639 * 1e18); + assertEq(_poolOne.bucketExchangeRate(2551), 1.000000116565164639 * 1e18); + assertEq(_poolOne.bucketExchangeRate(2552), 1.000000116565164639 * 1e18); + assertEq(_poolOne.bucketExchangeRate(2553), 1.000000116565164639 * 1e18); + assertEq(_poolOne.bucketExchangeRate(2555), 1.000000116565164639 * 1e18); _stakeToken(address(_poolOne), _minterThree, tokenIdThree); skip(1 days); @@ -951,30 +958,26 @@ contract RewardsManagerTest is DSTestPlus { pool: address(_poolOne), tokenId: tokenIdTwo, claimedArray: _epochsClaimedArray(1, 0), - reward: 20.035397317001861785 * 1e18, + reward: 20.035397317001861795 * 1e18, updateRatesReward: 0 }); uint256 minterTwoBalance = _ajnaToken.balanceOf(_minterTwo); - assertEq(minterTwoBalance, 20.035397317001861785 * 1e18); + assertEq(minterTwoBalance, 20.035397317001861795 * 1e18); _unstakeToken({ minter: _minterThree, pool: address(_poolOne), tokenId: tokenIdThree, claimedArray: _epochsClaimedArray(1, 0), - reward: 16.692493739675876000 * 1e18, + reward: 16.692493739675875940 * 1e18, updateRatesReward: 0 }); uint256 minterThreeBalance = _ajnaToken.balanceOf(_minterThree); - assertEq(minterThreeBalance, 16.692493739675876000 * 1e18); + assertEq(minterThreeBalance, 16.692493739675875940 * 1e18); assertGt(minterTwoBalance, minterThreeBalance); } - function testMultiPeriodRewardsMultiClaim() external { - - } - // Calling updateExchangeRates not needed since deposits will update the exchange rate themselves function testClaimRewardsMultipleDepositsSameBucketsMultipleAuctions() external { skip(10); @@ -1180,7 +1183,7 @@ contract RewardsManagerTest is DSTestPlus { updater: _updater, pool: address(_poolOne), depositIndexes: depositIndexes, - reward: 11.241216009399483348 * 1e18 + reward: 11.241216009399483350 * 1e18 }); _triggerReserveAuctions(TriggerReserveAuctionParams({ @@ -1209,24 +1212,24 @@ contract RewardsManagerTest is DSTestPlus { pool: address(_poolOne), epoch: 1, timestamp: block.timestamp - (52 weeks + 72 hours), - interest: 6447445050021308895, - burned: 81574747191341355205 + interest: 6.447445050021308895 * 1e18, + burned: 81.574747191341355205 * 1e18 }); _assertBurn({ pool: address(_poolOne), epoch: 2, timestamp: block.timestamp - (26 weeks + 48 hours), - burned: 306399067379332449973, - interest: 23974564976746846096 + burned: 306.399067379332450033 * 1e18, + interest: 23.974564976746846096 * 1e18 }); _assertBurn({ pool: address(_poolOne), epoch: 3, timestamp: block.timestamp - 24 hours, - burned: 699814215483322160364, - interest: 55764974712671474765 + burned: 699.814215483322160424 * 1e18, + interest: 55.764974712671474765 * 1e18 }); // both stakers claim rewards @@ -1235,7 +1238,7 @@ contract RewardsManagerTest is DSTestPlus { pool: address(_poolOne), tokenId: tokenIdOne, claimedArray: _epochsClaimedArray(3, 0), - reward: 58.317851290276861885 * 1e18, + reward: 58.317851290276861945 * 1e18, updateRatesReward: 0 }); @@ -1244,7 +1247,7 @@ contract RewardsManagerTest is DSTestPlus { pool: address(_poolOne), tokenId: tokenIdTwo, claimedArray: _epochsClaimedArray(3, 0), - reward: 291.589256451384309685 * 1e18, + reward: 291.589256451384309690 * 1e18, updateRatesReward: 0 }); } @@ -1328,14 +1331,41 @@ contract RewardsManagerTest is DSTestPlus { assertGt(_ajnaToken.balanceOf(_updater), 0); // check owner can withdraw the NFT and rewards will be automatically claimed + + uint256 snapshot = vm.snapshot(); + + // claimed rewards amount is greater than available tokens in rewards manager contract + + // burn rewards manager tokens and leave only 5 tokens available + changePrank(address(_rewardsManager)); + IERC20Token(address(_ajnaToken)).burn(99_999_990.978586345404952410 * 1e18); + + uint256 managerBalance = _ajnaToken.balanceOf(address(_rewardsManager)); + assertEq(managerBalance, 5 * 1e18); + changePrank(_minterOne); vm.expectEmit(true, true, true, true); - emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(1, 0), 40.214136545950568150 * 1e18); + emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(1, 0), 40.214136545950568100 * 1e18); + vm.expectEmit(true, true, true, true); + emit Unstake(_minterOne, address(_poolOne), tokenIdOne); + _rewardsManager.unstake(tokenIdOne); + + // minter one receives only the amount of 5 ajna tokens available in manager balance instead calculated rewards of 40.214136545950568150 + assertEq(_ajnaToken.balanceOf(_minterOne), managerBalance); + // all 5 tokens available in manager balance were used to reward minter one + assertEq(_ajnaToken.balanceOf(address(_rewardsManager)), 0); + + vm.revertTo(snapshot); + + // test when enough tokens in rewards manager contracts + changePrank(_minterOne); + vm.expectEmit(true, true, true, true); + emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(1, 0), 40.214136545950568100 * 1e18); vm.expectEmit(true, true, true, true); emit Unstake(_minterOne, address(_poolOne), tokenIdOne); _rewardsManager.unstake(tokenIdOne); assertEq(_positionManager.ownerOf(tokenIdOne), _minterOne); - assertEq(_ajnaToken.balanceOf(_minterOne), 40.214136545950568150 * 1e18); + assertEq(_ajnaToken.balanceOf(_minterOne), 40.214136545950568100 * 1e18); assertLt(_ajnaToken.balanceOf(_minterOne), tokensToBurn); uint256 currentBurnEpoch = _poolOne.currentBurnEpoch(); @@ -1376,6 +1406,7 @@ contract RewardsManagerTest is DSTestPlus { mintAmount: 1000 * 1e18, pool: _poolTwo }); + uint256 tokenIdTwo = _mintAndMemorializePositionNFT(mintMemorializeParams); // minterOne deposits their NFT into the rewards contract @@ -1391,6 +1422,7 @@ contract RewardsManagerTest is DSTestPlus { limitIndex: 3, pool: _poolOne }); + uint256 tokensToBurn = _triggerReserveAuctions(triggerReserveAuctionParams); uint256 currentBurnEpochPoolOne = _poolOne.currentBurnEpoch(); @@ -1418,9 +1450,9 @@ contract RewardsManagerTest is DSTestPlus { changePrank(_minterOne); assertEq(_ajnaToken.balanceOf(_minterOne), 4.021413654595047590 * 1e18); vm.expectEmit(true, true, true, true); - emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(1, 0), 40.214136545950568150 * 1e18); + emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(1, 0), 40.214136545950568100 * 1e18); _rewardsManager.claimRewards(tokenIdOne, currentBurnEpochPoolOne); - assertEq(_ajnaToken.balanceOf(_minterOne), 44.235550200545615740 * 1e18); + assertEq(_ajnaToken.balanceOf(_minterOne), 44.235550200545615690 * 1e18); assertLt(_ajnaToken.balanceOf(_minterOne), tokensToBurn); } @@ -1596,4 +1628,34 @@ contract RewardsManagerTest is DSTestPlus { } } + function testClaimRewardsFreezeUnclaimedYield() external { + skip(10); + + uint256[] memory depositIndexes = new uint256[](5); + depositIndexes[0] = 9; + depositIndexes[1] = 1; + depositIndexes[2] = 2; + depositIndexes[3] = 3; + depositIndexes[4] = 4; + MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ + indexes: depositIndexes, + minter: _minterOne, + mintAmount: 1000 * 1e18, + pool: _poolOne + }); + + uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); + _stakeToken(address(_poolOne), _minterOne, tokenIdOne); + + uint256 currentBurnEpoch = _poolOne.currentBurnEpoch(); + + changePrank(_minterOne); + // should revert if the epoch to claim is not available yet + vm.expectRevert(IRewardsManagerErrors.EpochNotAvailable.selector); + _rewardsManager.claimRewards(tokenIdOne, currentBurnEpoch + 10); + + // user should be able to claim rewards for current epoch + _rewardsManager.claimRewards(tokenIdOne, currentBurnEpoch); + } + } diff --git a/tests/forge/interactions/BalancerUniswapExample.sol b/tests/forge/interactions/BalancerUniswapExample.sol index 5b83d185c..d3b7c6eec 100644 --- a/tests/forge/interactions/BalancerUniswapExample.sol +++ b/tests/forge/interactions/BalancerUniswapExample.sol @@ -134,9 +134,9 @@ contract BalancerUniswapPurchaser { // approve ajna pool to transfer flash loaned collateral collateral.approve(decoded.ajnaPool, loanAmount); // purchase USDC with 1 WETH from ajna - uint256 lps = IAjnaPool(decoded.ajnaPool).addCollateral(loanAmount, decoded.bucketIndex); + uint256 lps = IAjnaPool(decoded.ajnaPool).addCollateral(loanAmount, decoded.bucketIndex, block.timestamp + 5 minutes); (uint256 quoteAmount, ) = IAjnaPool(decoded.ajnaPool).removeQuoteToken(type(uint256).max, decoded.bucketIndex); - assert(lps == 83008350.10362729922336157 * 1e27); // LPS in bucket + assert(lps == 83008350.10362729922336157 * 1e18); // LPS in bucket assert(quoteAmount == 4995.19230769230769 * 1e18); // Purchased quote amount assert(quote.balanceOf(address(this)) == 4995.192307 * 1e6); // USDC balance after Ajna purchase assert(collateral.balanceOf(address(this)) == 0); // WETH balance after Ajna purchase diff --git a/tests/forge/interactions/ERC20TakeWithExternalLiquidity.t.sol b/tests/forge/interactions/ERC20TakeWithExternalLiquidity.t.sol index f16a77e10..330352a96 100644 --- a/tests/forge/interactions/ERC20TakeWithExternalLiquidity.t.sol +++ b/tests/forge/interactions/ERC20TakeWithExternalLiquidity.t.sol @@ -54,11 +54,11 @@ contract ERC20TakeWithExternalLiquidityTest is Test { // add liquidity to the Ajna pool vm.startPrank(_lender); usdc.approve(address(_ajnaPool), type(uint256).max); - _ajnaPool.addQuoteToken(2_000 * 1e18, 3696); - _ajnaPool.addQuoteToken(5_000 * 1e18, 3698); - _ajnaPool.addQuoteToken(11_000 * 1e18, 3700); - _ajnaPool.addQuoteToken(25_000 * 1e18, 3702); - _ajnaPool.addQuoteToken(30_000 * 1e18, 3704); + _ajnaPool.addQuoteToken(2_000 * 1e18, 3696, type(uint256).max); + _ajnaPool.addQuoteToken(5_000 * 1e18, 3698, type(uint256).max); + _ajnaPool.addQuoteToken(11_000 * 1e18, 3700, type(uint256).max); + _ajnaPool.addQuoteToken(25_000 * 1e18, 3702, type(uint256).max); + _ajnaPool.addQuoteToken(30_000 * 1e18, 3704, type(uint256).max); vm.stopPrank(); // borrower draws debt @@ -132,4 +132,27 @@ contract ERC20TakeWithExternalLiquidityTest is Test { // confirm we earned some quote token assertGt(usdc.balanceOf(address(taker)), 0); } + + function testTakeCalleeDiffersFromSender() external { + + // _lender is msg.sender, QT & CT balances pre take + assertEq(usdc.balanceOf(_lender), 119_999.999999926999804658 * 1e18); + assertEq(weth.balanceOf(_lender), 0); + + // callee, _lender1 QT & CT balances pre take + assertEq(usdc.balanceOf(_lender1), 120_000.0 * 1e18); + assertEq(weth.balanceOf(_lender1), 4.0 * 1e18); + + // lender calls take, passing _lender1 as the callee + changePrank(_lender); + _ajnaPool.take(_borrower, 1_001 * 1e18, _lender1, new bytes(0)); + + // _lender is has QT deducted from balance + assertEq(usdc.balanceOf(_lender), 119_999.999999926985301196 * 1e18); + assertEq(weth.balanceOf(_lender), 0); + + // callee, _lender1 receives CT from take + assertEq(usdc.balanceOf(_lender1), 120_000.0 * 1e18); + assertEq(weth.balanceOf(_lender1), 6.0 * 1e18); + } } diff --git a/tests/forge/interactions/ERC721TakeWithExternalLiquidity.sol b/tests/forge/interactions/ERC721TakeWithExternalLiquidity.sol index 8e3327282..927b22a58 100644 --- a/tests/forge/interactions/ERC721TakeWithExternalLiquidity.sol +++ b/tests/forge/interactions/ERC721TakeWithExternalLiquidity.sol @@ -36,13 +36,11 @@ contract ERC721TakeWithExternalLiquidityTest is ERC721HelperContract { _quote.approve(address(_pool), type(uint256).max); // lender deposits 50_000 Quote into the pool - _addInitialLiquidity( - { - from: _lender, - amount: 50_000 * 1e18, - index: _i1004_98 - } - ); + _addInitialLiquidity({ + from: _lender, + amount: 50_000 * 1e18, + index: _i1004_98 + }); uint256[] memory tokenIdsToAdd = new uint256[](2); tokenIdsToAdd[0] = 1; @@ -90,4 +88,35 @@ contract ERC721TakeWithExternalLiquidityTest is ERC721HelperContract { // confirm we earned some quote token assertEq(_quote.balanceOf(address(taker)), 970.423096682230524352 * 1e18); } + + function testTakeNFTCalleeDiffersFromSender() external { + // instantiate and fund a hypothetical NFT marketplace + NFTMarketPlace marketPlace = new NFTMarketPlace(_quote); + deal(address(_quote), address(marketPlace), 25_000 * 1e18); + + // instantiate a taker contract which implements IERC721Taker and uses this marketplace + NFTTakeExample taker = new NFTTakeExample(address(marketPlace)); + changePrank(address(taker)); + assertEq(_quote.balanceOf(address(taker)), 0); + _quote.approve(address(_pool), type(uint256).max); + _collateral.setApprovalForAll(address(marketPlace), true); + + // _lender is msg.sender, QT & CT balances pre take + assertEq(_quote.balanceOf(_lender), 49_979.825641778370686641 * 1e18); + assertEq(_quote.balanceOf(address(taker)), 0); + + // call take using taker contract + changePrank(_lender); + bytes memory data = abi.encode(address(_pool)); + vm.expectEmit(true, true, false, true); + uint256 quoteTokenPaid = 529.576903317769475648 * 1e18; + uint256 collateralPurchased = 2 * 1e18; + uint256 bondChange = 5.295769033177694756 * 1e18; + emit Take(_borrower, quoteTokenPaid, collateralPurchased, bondChange, true); + _pool.take(_borrower, 2, address(taker), data); + + // _lender is msg.sender, QT & CT balances post take + assertEq(_quote.balanceOf(_lender), 49_450.248738460601210993 * 1e18); + assertEq(_quote.balanceOf(address(taker)), 1_500.0 * 1e18); // QT is increased as NFTTakeExample contract sells the NFT + } } diff --git a/tests/forge/interactions/Interfaces.sol b/tests/forge/interactions/Interfaces.sol index e00c7b003..3ed9cd8a6 100644 --- a/tests/forge/interactions/Interfaces.sol +++ b/tests/forge/interactions/Interfaces.sol @@ -13,7 +13,8 @@ interface IAjnaPool { function addCollateral( uint256 amount, - uint256 index + uint256 index, + uint256 expiry ) external returns (uint256 lpbChange); function removeQuoteToken( diff --git a/tests/forge/interactions/NFTTakeExample.sol b/tests/forge/interactions/NFTTakeExample.sol index 9a1e91687..78d730e26 100644 --- a/tests/forge/interactions/NFTTakeExample.sol +++ b/tests/forge/interactions/NFTTakeExample.sol @@ -58,6 +58,30 @@ contract NFTMarketPlace is INFTMarketPlace { currency.transfer(msg.sender, collectionOffer); } + /** @notice Implementing this method allows contracts to receive ERC721 tokens + * @dev https://forum.openzeppelin.com/t/erc721holder-ierc721receiver-and-onerc721received/11828 + */ + function onERC721Received(address, address, uint256, bytes memory) external pure returns (bytes4) { + return this.onERC721Received.selector; + } +} + +contract NFTNoopTakeExample is IERC721Taker { + uint256[] public tokenIdsReceived; + uint256 public quoteAmountDueReceived; + address public poolAddressReceived; + + function atomicSwapCallback( + uint256[] memory tokenIds, + uint256 quoteAmountDue, + bytes calldata data + ) external { + // NOOP, records inputs passed from pool to be checked in tests + tokenIdsReceived = tokenIds; + quoteAmountDueReceived = quoteAmountDue; + poolAddressReceived = abi.decode(data, (address)); + } + /** @notice Implementing this method allows contracts to receive ERC721 tokens * @dev https://forum.openzeppelin.com/t/erc721holder-ierc721receiver-and-onerc721received/11828 */ diff --git a/tests/forge/interactions/PurchaseQuoteWithExternalLiquidity.t.sol b/tests/forge/interactions/PurchaseQuoteWithExternalLiquidity.t.sol index 481be68c8..e9dfd6cfe 100644 --- a/tests/forge/interactions/PurchaseQuoteWithExternalLiquidity.t.sol +++ b/tests/forge/interactions/PurchaseQuoteWithExternalLiquidity.t.sol @@ -28,7 +28,7 @@ contract PurchaseQuoteWithExternalLiquidityTest is Test { deal(USDC, _lender, 120_000 * 1e6); vm.startPrank(_lender); usdc.approve(address(_ajnaPool), type(uint256).max); - _ajnaPool.addQuoteToken(5_000 * 1e18, 500); + _ajnaPool.addQuoteToken(5_000 * 1e18, 500, type(uint256).max); vm.stopPrank(); } diff --git a/tests/forge/utils/DSTestPlus.sol b/tests/forge/utils/DSTestPlus.sol index dcc00ab2e..ef1f25d0c 100644 --- a/tests/forge/utils/DSTestPlus.sol +++ b/tests/forge/utils/DSTestPlus.sol @@ -22,7 +22,7 @@ abstract contract DSTestPlus is Test, IPoolEvents { using EnumerableSet for EnumerableSet.UintSet; // nonce for generating random addresses - uint16 internal _nonce = 0; + uint256 internal _nonce = 0; // mainnet address of AJNA token, because tests are forked address internal _ajna = 0x9a96ec9B57Fb64FbC60B423d1f4da7691Bd35079; @@ -104,7 +104,7 @@ abstract contract DSTestPlus is Test, IPoolEvents { uint256 index ) internal { uint256 quoteTokenScale = IPool(address(_pool)).quoteTokenScale(); - uint256 lpAmount = (amount / quoteTokenScale) * quoteTokenScale * 1e9; + uint256 lpAmount = (amount / quoteTokenScale) * quoteTokenScale; _addLiquidity(from, amount, index, lpAmount, MAX_PRICE); } @@ -115,7 +115,7 @@ abstract contract DSTestPlus is Test, IPoolEvents { uint256 index ) internal { changePrank(from); - _pool.addQuoteToken(amount, index); + _pool.addQuoteToken(amount, index, type(uint256).max); // Add for tearDown lenders.add(from); @@ -136,7 +136,7 @@ abstract contract DSTestPlus is Test, IPoolEvents { vm.expectEmit(true, true, false, true); emit AddQuoteToken(from, index, (amount / quoteTokenScale) * quoteTokenScale, lpAward, newLup); _assertQuoteTokenTransferEvent(from, address(_pool), amount); - _pool.addQuoteToken(amount, index); + _pool.addQuoteToken(amount, index, type(uint256).max); // Add for tearDown lenders.add(from); @@ -246,7 +246,7 @@ abstract contract DSTestPlus is Test, IPoolEvents { vm.expectEmit(true, true, false, true); emit Kick(borrower, debt, collateral, bond); vm.expectEmit(true, true, false, true); - emit RemoveQuoteToken(from, index, removedFromDeposit, removedFromDeposit * 1e9, lup); + emit RemoveQuoteToken(from, index, removedFromDeposit, removedFromDeposit, lup); if(transferAmount != 0) _assertQuoteTokenTransferEvent(from, address(_pool), transferAmount); _pool.kickWithDeposit(index); } @@ -276,7 +276,7 @@ abstract contract DSTestPlus is Test, IPoolEvents { changePrank(from); vm.expectEmit(true, true, true, true); emit MoveQuoteToken(from, fromIndex, toIndex, amountMoved, lpRedeemFrom, lpAwardTo, newLup); - (uint256 lpbFrom, uint256 lpbTo) = _pool.moveQuoteToken(amount, fromIndex, toIndex); + (uint256 lpbFrom, uint256 lpbTo) = _pool.moveQuoteToken(amount, fromIndex, toIndex, type(uint256).max); assertEq(lpbFrom, lpRedeemFrom); assertEq(lpbTo, lpAwardTo); @@ -711,7 +711,7 @@ abstract contract DSTestPlus is Test, IPoolEvents { ) internal { changePrank(from); vm.expectRevert(abi.encodeWithSignature('BucketBankruptcyBlock()')); - _pool.addQuoteToken(amount, index); + _pool.addQuoteToken(amount, index, type(uint256).max); } function _assertAddLiquidityAtIndex0Revert( @@ -720,7 +720,7 @@ abstract contract DSTestPlus is Test, IPoolEvents { ) internal { changePrank(from); vm.expectRevert(IPoolErrors.InvalidIndex.selector); - _pool.addQuoteToken(amount, 0); + _pool.addQuoteToken(amount, 0, type(uint256).max); } function _assertAddLiquidityDustRevert( @@ -730,7 +730,18 @@ abstract contract DSTestPlus is Test, IPoolEvents { ) internal { changePrank(from); vm.expectRevert(IPoolErrors.DustAmountNotExceeded.selector); - _pool.addQuoteToken(amount, index); + _pool.addQuoteToken(amount, index, type(uint256).max); + } + + function _assertAddLiquidityExpiredRevert( + address from, + uint256 amount, + uint256 index, + uint256 expiry + ) internal { + changePrank(from); + vm.expectRevert(IPoolErrors.TransactionExpired.selector); + _pool.addQuoteToken(amount, index, expiry); } function _assertArbTakeNoAuction( @@ -1100,7 +1111,7 @@ abstract contract DSTestPlus is Test, IPoolEvents { ) internal { changePrank(from); vm.expectRevert(abi.encodeWithSignature('BucketBankruptcyBlock()')); - _pool.moveQuoteToken(amount, fromIndex, toIndex); + _pool.moveQuoteToken(amount, fromIndex, toIndex, type(uint256).max); } function _assertMoveLiquidityLupBelowHtpRevert( @@ -1111,7 +1122,19 @@ abstract contract DSTestPlus is Test, IPoolEvents { ) internal { changePrank(from); vm.expectRevert(IPoolErrors.LUPBelowHTP.selector); - _pool.moveQuoteToken(amount, fromIndex, toIndex); + _pool.moveQuoteToken(amount, fromIndex, toIndex, type(uint256).max); + } + + function _assertMoveLiquidityExpiredRevert( + address from, + uint256 amount, + uint256 fromIndex, + uint256 toIndex, + uint256 expiry + ) internal { + changePrank(from); + vm.expectRevert(IPoolErrors.TransactionExpired.selector); + _pool.moveQuoteToken(amount, fromIndex, toIndex, expiry); } function _assertMoveLiquidityDustRevert( @@ -1122,7 +1145,7 @@ abstract contract DSTestPlus is Test, IPoolEvents { ) internal { changePrank(from); vm.expectRevert(IPoolErrors.DustAmountNotExceeded.selector); - _pool.moveQuoteToken(amount, fromIndex, toIndex); + _pool.moveQuoteToken(amount, fromIndex, toIndex, type(uint256).max); } function _assertMoveLiquidityToSamePriceRevert( @@ -1133,7 +1156,7 @@ abstract contract DSTestPlus is Test, IPoolEvents { ) internal { changePrank(from); vm.expectRevert(IPoolErrors.MoveToSamePrice.selector); - _pool.moveQuoteToken(amount, fromIndex, toIndex); + _pool.moveQuoteToken(amount, fromIndex, toIndex, type(uint256).max); } function _assertMoveLiquidityToIndex0Revert( @@ -1143,7 +1166,7 @@ abstract contract DSTestPlus is Test, IPoolEvents { ) internal { changePrank(from); vm.expectRevert(IPoolErrors.InvalidIndex.selector); - _pool.moveQuoteToken(amount, fromIndex, 0); + _pool.moveQuoteToken(amount, fromIndex, 0, type(uint256).max); } function _assertMoveDepositLockedByAuctionDebtRevert( @@ -1154,7 +1177,7 @@ abstract contract DSTestPlus is Test, IPoolEvents { ) internal { changePrank(from); vm.expectRevert(IPoolErrors.RemoveDepositLockedByAuctionDebt.selector); - _pool.moveQuoteToken(amount, fromIndex, toIndex); + _pool.moveQuoteToken(amount, fromIndex, toIndex, type(uint256).max); } function _assertTakeAuctionInCooldownRevert( @@ -1253,6 +1276,14 @@ abstract contract DSTestPlus is Test, IPoolEvents { lup_ = _priceAt(lupIndex); } + function setRandomSeed(uint256 seed) public { + _nonce = seed; + } + + function getNextNonce() public returns (uint256) { + return _nonce == type(uint256).max ? 0 : ++_nonce; + } + function randomInRange(uint256 min, uint256 max) public returns (uint256) { return randomInRange(min, max, false); } @@ -1260,23 +1291,19 @@ abstract contract DSTestPlus is Test, IPoolEvents { function randomInRange(uint256 min, uint256 max, bool nonZero) public returns (uint256) { if (max == 0 && nonZero) return 1; else if (max == min) return max; - uint256 rand = uint(keccak256(abi.encodePacked(block.timestamp, msg.sender, _nonce))) % (max - min + 1) + min; - _nonce++; - return rand; + return uint(keccak256(abi.encodePacked(msg.sender, getNextNonce()))) % (max - min + 1) + min; } // returns a random index between 1 and 7388 function _randomIndex() internal returns (uint256 index_) { // calculate a random index between 1 and 7388 - index_ = 1 + uint256(keccak256(abi.encodePacked(block.number, block.difficulty))) % 7387; - vm.roll(block.number + 1); // advance block to ensure that the index price is different + index_ = 1 + uint256(keccak256(abi.encodePacked(msg.sender, getNextNonce()))) % 7387; } // returns a random index between 1 and a given maximum // used for testing in NFT pools where higher indexes (and lower prices) would require so many NFTs that gas and memory limits would be exceeded function _randomIndexWithMinimumPrice(uint256 minimumPrice_) internal returns (uint256 index_) { - index_ = 1 + uint256(keccak256(abi.encodePacked(block.number, block.difficulty))) % minimumPrice_; - vm.roll(block.number + 1); // advance block to ensure that the index price is different + index_ = 1 + uint256(keccak256(abi.encodePacked(msg.sender, getNextNonce()))) % minimumPrice_; } // find the bucket index in array corresponding to the highest bucket price diff --git a/tests/forge/utils/FenwickTreeInstance.sol b/tests/forge/utils/FenwickTreeInstance.sol index 7845d6116..784d74fea 100644 --- a/tests/forge/utils/FenwickTreeInstance.sol +++ b/tests/forge/utils/FenwickTreeInstance.sol @@ -68,42 +68,83 @@ contract FenwickTreeInstance is DSTestPlus { * @notice fills fenwick tree with fuzzed values and tests additions. */ function fuzzyFill( - uint256 insertions_, - uint256 amount_, - bool trackInserts) - external { - + uint256 insertions_, // number of insertions to perform + uint256 amount_, // total amount to insert + uint256 seed_, // seed for psuedorandom number generator + bool trackInserts + ) external { uint256 i; uint256 amount; + uint256 cumulativeAmount; // Calculate total insertions - uint256 insertsDec = bound(insertions_, 1000, 2000); + insertions_ = bound(insertions_, 1000, 2000); // Calculate total amount to insert - uint256 totalAmount = bound(amount_, 1 * 1e18, 9_000_000_000_000_000 * 1e18); - uint256 totalAmountDec = totalAmount; + amount_ = bound(amount_, 1 * 1e18, 9_000_000_000_000_000 * 1e18); + // Initialize and print seed for randomness + setRandomSeed(bound(seed_, 0, type(uint256).max - 1)); - while (totalAmountDec > 0 && insertsDec > 0) { + while (amount_ > 0 && insertions_ > 0) { // Insert at random index i = randomInRange(1, MAX_FENWICK_INDEX); // If last iteration, insert remaining - amount = insertsDec == 1 ? totalAmountDec : (totalAmountDec % insertsDec) * randomInRange(1_000, 1 * 1e10, true); + amount = insertions_ == 1 ? amount_ : (amount_ % insertions_) * randomInRange(1_000, 1 * 1e10, true); // Update values add(i, amount); - totalAmountDec -= amount; - insertsDec -= 1; + amount_ -= amount; + insertions_ -= 1; + cumulativeAmount += amount; // Verify tree sum - assertEq(deposits.treeSum(), totalAmount - totalAmountDec); + assertEq(deposits.treeSum(), cumulativeAmount); if (trackInserts) inserts.push(i); } - assertEq(deposits.treeSum(), totalAmount); + assertEq(deposits.treeSum(), cumulativeAmount); } -} + /** + * @notice fills fenwick tree with deterministic values and tests additions. + */ + function nonFuzzyFill( + uint256 insertions_, // number of insertions to perform + uint256 amount_, // total amount to insert + uint256 seed_, // seed for psuedorandom number generator + bool trackInserts + ) external { + uint256 i; + uint256 amount; + uint256 cumulativeAmount; + + // Initialize and print seed for randomness + setRandomSeed(seed_); + + while (amount_ > 0 && insertions_ > 0) { + + // Insert at random index + i = randomInRange(1, MAX_FENWICK_INDEX); + + // If last iteration, insert remaining + amount = insertions_ == 1 ? amount_ : (amount_ % insertions_) * randomInRange(1_000, 1 * 1e10, true); + + // Update values + add(i, amount); + amount_ -= amount; + insertions_ -= 1; + cumulativeAmount += amount; + + // Verify tree sum + assertEq(deposits.treeSum(), cumulativeAmount); + + if (trackInserts) inserts.push(i); + } + + assertEq(deposits.treeSum(), cumulativeAmount); + } +} diff --git a/tests/forge/utils/FlashloanBorrower.sol b/tests/forge/utils/FlashloanBorrower.sol index 55e701d8f..a23db27eb 100644 --- a/tests/forge/utils/FlashloanBorrower.sol +++ b/tests/forge/utils/FlashloanBorrower.sol @@ -1,8 +1,11 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.14; +import '@openzeppelin/contracts/token/ERC20/ERC20.sol'; + import { Token } from '../utils/Tokens.sol'; +import 'src/ERC20Pool.sol'; import 'src/interfaces/pool/IERC3156FlashBorrower.sol'; import 'src/libraries/internal/Maths.sol'; @@ -32,10 +35,30 @@ contract FlashloanBorrower is IERC3156FlashBorrower { // Example of some defi strategy which produces a fixed return contract SomeDefiStrategy { - Token public token; + ERC20 public token; + + constructor(ERC20 token_) { + token = token_; + } + + function makeMoney(uint256 amount_) external { + // step 1: take deposit from caller + token.transferFrom(msg.sender, address(this), amount_); + // step 2: earn 3.5% reward + uint256 reward = Maths.wmul(0.035 * 1e18, amount_); + // step 3: profit + token.transfer(msg.sender, amount_ + reward); + } +} - constructor(Token token_) { +// Example of some defi strategy which repays to pool +contract SomeDefiStrategyWithRepayment { + ERC20 public token; + address public pool; + + constructor(ERC20 token_, address pool_) { token = token_; + pool = pool_; } function makeMoney(uint256 amount_) external { @@ -45,5 +68,8 @@ contract SomeDefiStrategy { uint256 reward = Maths.wmul(0.035 * 1e18, amount_); // step 3: profit token.transfer(msg.sender, amount_ + reward); + + // repay amount to pool + token.transfer(pool, 1 * 1e18); } } \ No newline at end of file diff --git a/tests/forge/utils/HeapInstance.sol b/tests/forge/utils/HeapInstance.sol index 779961ef9..3cc509799 100644 --- a/tests/forge/utils/HeapInstance.sol +++ b/tests/forge/utils/HeapInstance.sol @@ -60,10 +60,10 @@ contract HeapInstance is DSTestPlus { * @notice fills Heap with fuzzed values and tests additions. */ function fuzzyFill( - uint256 inserts_, - bool trackInserts_) - external { - + uint256 inserts_, // number of insertions to perform + uint256 seed_, // seed for psuedorandom number generator + bool trackInserts_ + ) external { uint256 tp; address borrower; @@ -71,6 +71,9 @@ contract HeapInstance is DSTestPlus { uint256 totalInserts = bound(inserts_, 1000, 2000); uint256 insertsDec = totalInserts; + // Initialize and print seed for randomness + setRandomSeed(bound(seed_, 0, type(uint256).max - 1)); + while (insertsDec > 0) { // build address and TP @@ -91,4 +94,3 @@ contract HeapInstance is DSTestPlus { assertEq(_heap.loans.length - 1, totalInserts); } } -