diff --git a/contracts/facade/FacadeRedeem.sol b/contracts/facade/FacadeRedeem.sol new file mode 100644 index 0000000000..5264df66ce --- /dev/null +++ b/contracts/facade/FacadeRedeem.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity 0.8.19; + +import "../interfaces/IRToken.sol"; +import "../libraries/Fixed.sol"; +import "../p1/BasketHandler.sol"; +import "../p1/BackingManager.sol"; + +contract FacadeRedeem { + function getBaseInformation(IRToken rToken) + external + returns ( + uint48 currentNonce, + uint192 basketsNeeded, + IERC20[] memory allErc20s, + uint256[] memory allBalances, + IERC20[][] memory erc20s, + uint256[][] memory quantities + ) + { + // Either hell freezes over, or it's a static call. + require(msg.sender == address(0), unicode"ಠ_ಠ"); + + rToken.main().poke(); + + BackingManagerP1 bm = BackingManagerP1(address(rToken.main().backingManager())); + BasketHandlerP1 bh = BasketHandlerP1(address(rToken.main().basketHandler())); + + currentNonce = bh.nonce(); + basketsNeeded = rToken.basketsNeeded(); + + allErc20s = rToken.main().assetRegistry().erc20s(); + allBalances = new uint256[](allErc20s.length); + + for (uint256 i = 0; i < allErc20s.length; ++i) { + allBalances[i] = allErc20s[i].balanceOf(address(bm)); + } + + quantities = new uint256[][](currentNonce); + erc20s = new IERC20[][](currentNonce); + + for (uint48 nonce = 1; nonce <= currentNonce; nonce++) { + (IERC20[] memory erc20ForNonce, uint256[] memory qtys) = bh.getHistoricalBasket(nonce); + + quantities[nonce - 1] = qtys; + erc20s[nonce - 1] = erc20ForNonce; + } + } +} diff --git a/scripts/redeem.ts b/scripts/redeem.ts new file mode 100644 index 0000000000..5c456837d5 --- /dev/null +++ b/scripts/redeem.ts @@ -0,0 +1,125 @@ +import { BigNumber } from 'ethers' +import hre, { ethers } from 'hardhat' + +const bigIntMax = (...args: BigNumber[]) => args.reduce((m, e) => (e.gt(m) ? e : m)) +const bigIntMin = (...args: BigNumber[]) => args.reduce((m, e) => (e.lt(m) ? e : m)) + +const rTokenAddress = '0xA0d69E286B938e21CBf7E51D71F6A4c8918f482F' // eUSD +// const rTokenAddress = '0xaCdf0DBA4B9839b96221a8487e9ca660a48212be' // hyUSD + +async function main() { + const FacadeRedeemFactory = await hre.ethers.getContractFactory('FacadeRedeem') + const FacadeRedeem = await FacadeRedeemFactory.deploy().then((e) => e.deployed()) + + const baseData = await FacadeRedeem.connect(hre.ethers.provider).callStatic.getBaseInformation( + rTokenAddress, + { + from: ethers.constants.AddressZero, + } + ) + + console.log({ currentNonce: baseData.currentNonce }) + console.log({ basketsNeeded: baseData.basketsNeeded }) + + // Nonces are mapped to their _indexes_ aka, nonce 3 becomes 2, nonce 4 becomes 3 and so on. + const allValidNonces = Array.from({ length: baseData.currentNonce }, (_, i) => i).filter((e) => { + // If basket has an erc20 which was unregistered. + if ( + baseData.erc20s[e].filter((f) => baseData.allErc20s.includes(f)).length !== + baseData.erc20s[e].length + ) { + return false + } + + // If basket has no erc20s, aka before 3.1.0 release. + if (baseData.erc20s[e].length === 0) { + return false + } + + return true + }) + + console.log({ allValidNonces }) + + // Final picked nonces + const pickedNonces: Record = {} + + // Just temp storage + const nonceFractions: Record = {} + const activeBalances = [...baseData.allBalances] + + function getBalanceFor(erc20: string) { + return activeBalances[baseData.allErc20s.indexOf(erc20)] + } + + // Worst case, we'll need some fraction of each basket. + let validNonces = [...allValidNonces] + while (validNonces.length > 0) { + for (const nonce of validNonces) { + const basketFraction = baseData.erc20s[nonce].map((e, i) => + baseData.quantities[nonce][i].eq(0) + ? BigNumber.from(0) + : getBalanceFor(e).mul(BigNumber.from(10).pow(36)).div(baseData.quantities[nonce][i]) + ) + + const basketFractionRatio = basketFraction.map((e) => e.div(baseData.basketsNeeded)) + + // console.log(basketFraction, basketFractionRatio) + + nonceFractions[nonce] = bigIntMin(...basketFractionRatio) + + // if (nonceFractions[nonce].eq(0)) { + // // remove fraction if it gives Infinity, it just means the list was empty. + // validNonces = validNonces.filter((e) => e !== nonce) + // } + + // console.log(basketFraction, basketFractionRatio, nonceFractions[nonce]) + } + + // Let's pick the nonce with the max redemption value. + const chosenNonce = validNonces.find( + (e) => nonceFractions[e] == bigIntMax(...validNonces.map((e) => nonceFractions[e])) + )! + pickedNonces[chosenNonce] = nonceFractions[chosenNonce] + + // console.log({ chosenNonce }) + // console.log(baseData.quantities[chosenNonce]) + + baseData.erc20s[chosenNonce].forEach((e, i) => { + // console.log(getBalanceFor(e)) + // console.log( + // baseData.quantities[chosenNonce][i] + // .mul(nonceFractions[chosenNonce]) + // .mul(baseData.basketsNeeded) + // .div(BigNumber.from(10).pow(36)) + // ) + + activeBalances[baseData.allErc20s.indexOf(e)] = getBalanceFor(e).sub( + baseData.quantities[chosenNonce][i] + .mul(nonceFractions[chosenNonce]) + .mul(baseData.basketsNeeded) + .div(BigNumber.from(10).pow(36)) + ) + + if (getBalanceFor(e).lt(0)) { + activeBalances[baseData.allErc20s.indexOf(e)] = BigNumber.from(0) + } + + // console.log(activeBalances[baseData.allErc20s.indexOf(e)]) + // console.log('next') + }) + + validNonces = validNonces.filter((e) => e !== chosenNonce) + + // console.log(chosenNonce, nonceFractions[chosenNonce]) + } + + console.log({ pickedNonces }) +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error) + process.exit(1) + })