diff --git a/Makefile b/Makefile index eb039b60..1b4d5beb 100644 --- a/Makefile +++ b/Makefile @@ -464,6 +464,10 @@ zksync-bytecode-hashes: redeploy-market: make SCRIPT_PATH=./scripts/dev/15.redeploy-market.ts run +.PHONY: upgrade-pool-aa-position-mover +upgrade-pool-aa-position-mover: + make TASK_NAME=upgrade:pool-aa-position-mover run-task + .PHONY: transfer-tokens transfer-tokens: make SCRIPT_PATH=./scripts/dev/2.transfer-tokens.ts run diff --git a/contracts/interfaces/IAccountFactory.sol b/contracts/interfaces/IAccountFactory.sol new file mode 100644 index 00000000..195d97b9 --- /dev/null +++ b/contracts/interfaces/IAccountFactory.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity ^0.8.10; + +interface IAccountFactory { + function createAccount( + address owner, + uint256 salt + ) external returns (address); +} diff --git a/contracts/interfaces/INToken.sol b/contracts/interfaces/INToken.sol index b61282be..552fbc15 100644 --- a/contracts/interfaces/INToken.sol +++ b/contracts/interfaces/INToken.sol @@ -5,7 +5,7 @@ import {IERC721} from "../dependencies/openzeppelin/contracts/IERC721.sol"; import {IERC721Receiver} from "../dependencies/openzeppelin/contracts/IERC721Receiver.sol"; import {IERC721Enumerable} from "../dependencies/openzeppelin/contracts/IERC721Enumerable.sol"; import {IERC1155Receiver} from "../dependencies/openzeppelin/contracts/IERC1155Receiver.sol"; - +import {ICollateralizableERC721} from "./ICollateralizableERC721.sol"; import {IInitializableNToken} from "./IInitializableNToken.sol"; import {IXTokenType} from "./IXTokenType.sol"; import {DataTypes} from "../protocol/libraries/types/DataTypes.sol"; @@ -16,6 +16,7 @@ import {DataTypes} from "../protocol/libraries/types/DataTypes.sol"; * @notice Defines the basic interface for an NToken. **/ interface INToken is + ICollateralizableERC721, IERC721Enumerable, IInitializableNToken, IERC721Receiver, diff --git a/contracts/interfaces/IPool.sol b/contracts/interfaces/IPool.sol index 0839b4c3..f099ff02 100644 --- a/contracts/interfaces/IPool.sol +++ b/contracts/interfaces/IPool.sol @@ -6,6 +6,7 @@ import {IPoolMarketplace} from "./IPoolMarketplace.sol"; import {IPoolParameters} from "./IPoolParameters.sol"; import {IParaProxyInterfaces} from "./IParaProxyInterfaces.sol"; import {IPoolPositionMover} from "./IPoolPositionMover.sol"; +import {IPoolAAPositionMover} from "./IPoolAAPositionMover.sol"; import "./IPoolApeStaking.sol"; import "./IPoolBorrowAndStake.sol"; @@ -21,7 +22,8 @@ interface IPool is IPoolApeStaking, IParaProxyInterfaces, IPoolPositionMover, - IPoolBorrowAndStake + IPoolBorrowAndStake, + IPoolAAPositionMover { } diff --git a/contracts/interfaces/IPoolAAPositionMover.sol b/contracts/interfaces/IPoolAAPositionMover.sol new file mode 100644 index 00000000..f8ad261a --- /dev/null +++ b/contracts/interfaces/IPoolAAPositionMover.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity ^0.8.0; + +/** + * @title IPool + * + * @notice Defines the basic interface for an ParaSpace Pool. + **/ +interface IPoolAAPositionMover { + event PositionMovedToAA(address indexed user, address aaAccount); + + function positionMoveToAA(address aaAccount) external; +} diff --git a/contracts/protocol/libraries/helpers/Errors.sol b/contracts/protocol/libraries/helpers/Errors.sol index c10efa7b..ece1ab75 100644 --- a/contracts/protocol/libraries/helpers/Errors.sol +++ b/contracts/protocol/libraries/helpers/Errors.sol @@ -135,4 +135,5 @@ library Errors { string public constant TOKEN_NOT_ALLOW_RESCUE = "140"; // token is not allow rescue string public constant INVALID_PARAMETER = "170"; //invalid parameter + string public constant INVALID_CALLER = "171"; //invalid callser } diff --git a/contracts/protocol/pool/PoolAAPositionMove.sol b/contracts/protocol/pool/PoolAAPositionMove.sol new file mode 100644 index 00000000..7e290e2a --- /dev/null +++ b/contracts/protocol/pool/PoolAAPositionMove.sol @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import {ParaVersionedInitializable} from "../libraries/paraspace-upgradeability/ParaVersionedInitializable.sol"; +import {DataTypes} from "../libraries/types/DataTypes.sol"; +import {IPoolAAPositionMover} from "../../interfaces/IPoolAAPositionMover.sol"; +import {IAccount} from "../../interfaces/IAccount.sol"; +import {PoolStorage} from "./PoolStorage.sol"; +import {ParaReentrancyGuard} from "../libraries/paraspace-upgradeability/ParaReentrancyGuard.sol"; +import {IPool} from "../../interfaces/IPool.sol"; +import {INToken} from "../../interfaces/INToken.sol"; +import {IPToken} from "../../interfaces/IPToken.sol"; +import {Errors} from "../libraries/helpers/Errors.sol"; +import {ReserveConfiguration} from "../../protocol/libraries/configuration/ReserveConfiguration.sol"; +import {UserConfiguration} from "../../protocol/libraries/configuration/UserConfiguration.sol"; +import {ReserveLogic} from "../libraries/logic/ReserveLogic.sol"; +import {SafeCast} from "../../dependencies/openzeppelin/contracts/SafeCast.sol"; +import {IAccountFactory} from "../../interfaces/IAccountFactory.sol"; +import {Errors} from "../libraries/helpers/Errors.sol"; +import {IVariableDebtToken} from "../../interfaces/IVariableDebtToken.sol"; +import {MathUtils} from "../libraries/math/MathUtils.sol"; +import {WadRayMath} from "../libraries/math/WadRayMath.sol"; + +/** + * @title Pool PositionMover contract + * + **/ +contract PoolAAPositionMover is + ParaVersionedInitializable, + ParaReentrancyGuard, + PoolStorage, + IPoolAAPositionMover +{ + uint256 internal constant POOL_REVISION = 200; + + event ReserveUsedAsCollateralEnabled( + address indexed reserve, + address indexed user + ); + event ReserveUsedAsCollateralDisabled( + address indexed reserve, + address indexed user + ); + + using ReserveConfiguration for DataTypes.ReserveConfigurationMap; + using UserConfiguration for DataTypes.UserConfigurationMap; + using ReserveLogic for DataTypes.ReserveData; + using SafeCast for uint256; + using WadRayMath for uint256; + + struct MigrationCacheVars { + uint256 reservesCount; + bool isTokenCollateral; + bool isCollectionCollateral; + DataTypes.TimeLockParams timeLockParams; + } + + function positionMoveToAA(address aaAccount) external nonReentrant { + require( + IAccount(aaAccount).owner() == msg.sender, + Errors.NOT_THE_OWNER + ); + + DataTypes.PoolStorage storage ps = poolStorage(); + DataTypes.UserConfigurationMap storage userConfig = ps._usersConfig[ + msg.sender + ]; + DataTypes.UserConfigurationMap storage aaConfig = ps._usersConfig[ + aaAccount + ]; + + MigrationCacheVars memory cacheVars; + cacheVars.reservesCount = ps._reservesCount; + for (uint256 j = 0; j < cacheVars.reservesCount; j++) { + address currentReserveAddress = ps._reservesList[j]; + if (currentReserveAddress == address(0)) { + continue; + } + + DataTypes.ReserveCache memory reserveCache = ps + ._reserves[currentReserveAddress] + .cache(); + if ( + reserveCache.reserveConfiguration.getAssetType() == + DataTypes.AssetType.ERC20 + ) { + //handle ptoken + { + IPToken pToken = IPToken(reserveCache.xTokenAddress); + uint256 balance = pToken.balanceOf(msg.sender); + if (balance > 0) { + uint256 nextLiquidityIndex = _calculateLiquidityIndex( + reserveCache + ); + pToken.burn( + msg.sender, + reserveCache.xTokenAddress, + balance, + nextLiquidityIndex, + cacheVars.timeLockParams + ); + pToken.mint( + aaAccount, + aaAccount, + balance, + nextLiquidityIndex + ); + if (userConfig.isUsingAsCollateral(j)) { + aaConfig.setUsingAsCollateral(j, true); + emit ReserveUsedAsCollateralEnabled( + currentReserveAddress, + aaAccount + ); + userConfig.setUsingAsCollateral(j, false); + emit ReserveUsedAsCollateralDisabled( + currentReserveAddress, + msg.sender + ); + } + } + } + + //handle debt token + { + IVariableDebtToken debtToken = IVariableDebtToken( + ps + ._reserves[currentReserveAddress] + .variableDebtTokenAddress + ); + uint256 balance = debtToken.balanceOf(msg.sender); + if (balance > 0) { + uint256 debtIndex = _calculateDebtIndex(reserveCache); + debtToken.burn(msg.sender, balance, debtIndex); + debtToken.mint( + aaAccount, + aaAccount, + balance, + debtIndex + ); + aaConfig.setBorrowing(j, true); + userConfig.setBorrowing(j, false); + } + } + } else { + INToken nToken = INToken(reserveCache.xTokenAddress); + uint256 balance = nToken.balanceOf(msg.sender); + cacheVars.isCollectionCollateral = false; + for (uint256 k = 0; k < balance; k++) { + uint256 tokenId = nToken.tokenOfOwnerByIndex(msg.sender, k); + cacheVars.isTokenCollateral = nToken.isUsedAsCollateral( + tokenId + ); + nToken.transferOnLiquidation( + msg.sender, + aaAccount, + tokenId + ); + if (cacheVars.isTokenCollateral) { + nToken.setIsUsedAsCollateral(tokenId, true, aaAccount); + cacheVars.isCollectionCollateral = true; + } + } + if (cacheVars.isCollectionCollateral) { + aaConfig.setUsingAsCollateral(j, true); + emit ReserveUsedAsCollateralEnabled( + currentReserveAddress, + aaAccount + ); + userConfig.setUsingAsCollateral(j, false); + emit ReserveUsedAsCollateralDisabled( + currentReserveAddress, + msg.sender + ); + } + } + } + + emit PositionMovedToAA(msg.sender, aaAccount); + } + + function getRevision() internal pure virtual override returns (uint256) { + return POOL_REVISION; + } + + function _calculateLiquidityIndex( + DataTypes.ReserveCache memory reserveCache + ) internal view returns (uint256) { + uint256 cumulatedLiquidityInterest = MathUtils.calculateLinearInterest( + reserveCache.currLiquidityRate, + reserveCache.reserveLastUpdateTimestamp + ); + return + cumulatedLiquidityInterest.rayMul(reserveCache.currLiquidityIndex); + } + + function _calculateDebtIndex( + DataTypes.ReserveCache memory reserveCache + ) internal view returns (uint256) { + uint256 cumulatedVariableBorrowInterest = MathUtils + .calculateCompoundedInterest( + reserveCache.currVariableBorrowRate, + reserveCache.reserveLastUpdateTimestamp + ); + return + cumulatedVariableBorrowInterest.rayMul( + reserveCache.currVariableBorrowIndex + ); + } +} diff --git a/helpers/contracts-deployments.ts b/helpers/contracts-deployments.ts index 25a9ac4f..ebce79b1 100644 --- a/helpers/contracts-deployments.ts +++ b/helpers/contracts-deployments.ts @@ -153,6 +153,7 @@ import { WstETHMocked, X2Y2Adapter, X2Y2R1, + PoolAAPositionMover__factory, PoolBorrowAndStake__factory, PoolBorrowAndStake, } from "../types"; @@ -671,6 +672,27 @@ export const deployPoolParaProxyInterfaces = async (verify?: boolean) => { }; }; +export const deployAAPoolPositionMover = async (verify?: boolean) => { + const {poolAAPositionMoverSelectors} = await getPoolSignatures(); + + const poolAAPositionMover = (await withSaveAndVerify( + await getContractFactory("PoolAAPositionMover"), + eContractid.PoolAAPositionMoverImpl, + [], + verify, + false, + undefined, + poolAAPositionMoverSelectors + )) as PoolPositionMover; + + return { + poolAAPositionMover, + poolAAPositionMoverSelectors: poolAAPositionMoverSelectors.map( + (s) => s.signature + ), + }; +}; + export const deployPoolPositionMover = async ( provider: tEthereumAddress, bendDaoLendPoolLoan: tEthereumAddress, @@ -786,6 +808,10 @@ export const getPoolSignatures = () => { PoolPositionMover__factory.abi ); + const poolAAPositionMoverSelectors = getFunctionSignatures( + PoolAAPositionMover__factory.abi + ); + const poolProxySelectors = getFunctionSignatures(ParaProxy__factory.abi); const poolParaProxyInterfacesSelectors = getFunctionSignatures( @@ -802,6 +828,7 @@ export const getPoolSignatures = () => { ...poolProxySelectors, ...poolParaProxyInterfacesSelectors, ...poolPositionMoverSelectors, + ...poolAAPositionMoverSelectors, ]; for (const selector of poolSelectors) { if (!allSelectors[selector.signature]) { @@ -823,6 +850,7 @@ export const getPoolSignatures = () => { poolBorrowAndStakeSelectors, poolParaProxyInterfacesSelectors, poolPositionMoverSelectors, + poolAAPositionMoverSelectors, }; }; @@ -3210,15 +3238,21 @@ export const deployAccount = async ( ) as Promise; export const deployAccountFactory = async ( - accountRegistry: tEthereumAddress, + entryPoint: tEthereumAddress, verify?: boolean -) => - withSaveAndVerify( +) => { + const accountImpl = await deployAccount(entryPoint, verify); + const accountRegistry = await deployAccountRegistry( + accountImpl.address, + verify + ); + return withSaveAndVerify( await getContractFactory("AccountFactory"), eContractid.AccountFactory, - [accountRegistry], + [accountRegistry.address], verify ) as Promise; +}; export const deployAccountRegistry = async ( impl: tEthereumAddress, diff --git a/helpers/contracts-getters.ts b/helpers/contracts-getters.ts index 033947d2..be45e866 100644 --- a/helpers/contracts-getters.ts +++ b/helpers/contracts-getters.ts @@ -98,6 +98,7 @@ import { MockLendPool__factory, NTokenChromieSquiggle__factory, Account__factory, + AccountFactory__factory, AccountRegistry__factory, } from "../types"; import { @@ -1336,6 +1337,17 @@ export const getAccountRegistry = async (address?: tEthereumAddress) => await getFirstSigner() ); +export const getAccountFactory = async (address?: tEthereumAddress) => + await AccountFactory__factory.connect( + address || + ( + await getDb() + .get(`${eContractid.AccountFactory}.${DRE.network.name}`) + .value() + ).address, + await getFirstSigner() + ); + //////////////////////////////////////////////////////////////////////////////// // MOCK //////////////////////////////////////////////////////////////////////////////// diff --git a/helpers/hardhat-constants.ts b/helpers/hardhat-constants.ts index 30dd2c73..e0637a21 100644 --- a/helpers/hardhat-constants.ts +++ b/helpers/hardhat-constants.ts @@ -265,6 +265,9 @@ export const ZK_LIBRARIES = fs.existsSync(ZK_LIBRARIES_PATH) "contracts/protocol/libraries/logic/SupplyLogic.sol": { SupplyLogic: ZERO_ADDRESS, }, + "contracts/protocol/libraries/logic/SupplyExtendedLogic.sol": { + SupplyExtendedLogic: ZERO_ADDRESS, + }, "contracts/protocol/libraries/logic/LiquidationLogic.sol": { LiquidationLogic: ZERO_ADDRESS, }, diff --git a/helpers/types.ts b/helpers/types.ts index 309d65b2..3b91198b 100644 --- a/helpers/types.ts +++ b/helpers/types.ts @@ -233,6 +233,7 @@ export enum eContractid { MockAirdropProject = "MockAirdropProject", PoolCoreImpl = "PoolCoreImpl", PoolMarketplaceImpl = "PoolMarketplaceImpl", + PoolAAPositionMoverImpl = "PoolAAPositionMoverImpl", PoolParametersImpl = "PoolParametersImpl", PoolApeStakingImpl = "PoolApeStakingImpl", PoolBorrowAndStakeImpl = "PoolBorrowAndStakeImpl", diff --git a/scripts/deployments/steps/06_pool.ts b/scripts/deployments/steps/06_pool.ts index 3348f9d8..2776c069 100644 --- a/scripts/deployments/steps/06_pool.ts +++ b/scripts/deployments/steps/06_pool.ts @@ -1,5 +1,6 @@ import {ZERO_ADDRESS} from "../../../helpers/constants"; import { + deployAAPoolPositionMover, deployMockBendDaoLendPool, deployPoolComponents, deployPoolParaProxyInterfaces, @@ -121,6 +122,24 @@ export const step_06 = async (verify = false) => { ); } + const {poolAAPositionMover, poolAAPositionMoverSelectors} = + await deployAAPoolPositionMover(verify); + + await waitForTx( + await addressesProvider.updatePoolImpl( + [ + { + implAddress: poolAAPositionMover.address, + action: 0, + functionSelectors: poolAAPositionMoverSelectors, + }, + ], + ZERO_ADDRESS, + "0x", + GLOBAL_OVERRIDES + ) + ); + if (poolApeStaking) { await waitForTx( await addressesProvider.updatePoolImpl( diff --git a/scripts/deployments/steps/24_accountAbstraction.ts b/scripts/deployments/steps/24_accountAbstraction.ts index 4bca47b5..46399bdb 100644 --- a/scripts/deployments/steps/24_accountAbstraction.ts +++ b/scripts/deployments/steps/24_accountAbstraction.ts @@ -1,8 +1,4 @@ -import { - deployAccount, - deployAccountFactory, - deployAccountRegistry, -} from "../../../helpers/contracts-deployments"; +import {deployAccountFactory} from "../../../helpers/contracts-deployments"; import {getParaSpaceConfig, isLocalTestnet} from "../../../helpers/misc-utils"; import {Client} from "userop"; @@ -12,19 +8,8 @@ export const step_24 = async (verify = false) => { if (!isLocalTestnet()) { const client = Client.init(paraSpaceConfig.AccountAbstraction.rpcUrl); - const account = await deployAccount( - ( - await client - ).entryPoint.address, - verify - ); - - const accountRegistry = await deployAccountRegistry( - account.address, - verify - ); - - await deployAccountFactory(accountRegistry.address, verify); + const entryPoint = (await client).entryPoint.address; + await deployAccountFactory(entryPoint, verify); } } catch (error) { console.error(error); diff --git a/scripts/upgrade/pool.ts b/scripts/upgrade/pool.ts index ac149d4b..0166a210 100644 --- a/scripts/upgrade/pool.ts +++ b/scripts/upgrade/pool.ts @@ -7,6 +7,7 @@ import { deployPoolMarketplace, deployPoolParameters, deployPoolPositionMover, + deployAAPoolPositionMover, } from "../../helpers/contracts-deployments"; import { getAllTokens, @@ -385,3 +386,28 @@ export const upgradePoolPositionMover = async ( await upgradeProxyImplementations(implementations); }; + +export const upgradePoolAAPositionMover = async ( + oldAAPoolPositionMover: tEthereumAddress, + verify = false +) => { + const pool = await getPoolProxy(); + const oldPoolPositionMoverSelectors = await pool.facetFunctionSelectors( + oldAAPoolPositionMover + ); + + const { + poolAAPositionMover, + poolAAPositionMoverSelectors: newPoolPositionMoverSelectors, + } = await deployAAPoolPositionMover(verify); + + const implementations = [ + [ + poolAAPositionMover.address, + newPoolPositionMoverSelectors, + oldPoolPositionMoverSelectors, + ], + ] as [string, string[], string[]][]; + + await upgradeProxyImplementations(implementations); +}; diff --git a/tasks/upgrade/index.ts b/tasks/upgrade/index.ts index fda8cb44..bb192094 100644 --- a/tasks/upgrade/index.ts +++ b/tasks/upgrade/index.ts @@ -102,6 +102,21 @@ task("upgrade:pool-position-mover", "upgrade pool position mover") console.timeEnd("upgrade poolPositionMover"); }); +task("upgrade:pool-aa-position-mover", "upgrade pool aa position mover") + .addPositionalParam("oldPoolAAPositionMover", "old pool aa position mover") + .setAction(async ({oldPoolAAPositionMover}, DRE) => { + const {upgradePoolAAPositionMover} = await import( + "../../scripts/upgrade/pool" + ); + await DRE.run("set-DRE"); + console.time("upgrade poolAAPositionMover"); + await upgradePoolAAPositionMover( + oldPoolAAPositionMover, + ETHERSCAN_VERIFICATION + ); + console.timeEnd("upgrade poolAAPositionMover"); + }); + task("upgrade:configurator", "upgrade pool configurator").setAction( async (_, DRE) => { const {upgradeConfigurator} = await import( diff --git a/test/_aa_migration.spec.ts b/test/_aa_migration.spec.ts new file mode 100644 index 00000000..90cb8da3 --- /dev/null +++ b/test/_aa_migration.spec.ts @@ -0,0 +1,206 @@ +import {testEnvFixture} from "./helpers/setup-env"; +import {loadFixture} from "@nomicfoundation/hardhat-network-helpers"; +import {getVariableDebtToken} from "../helpers/contracts-getters"; +import {expect} from "chai"; +import { + convertToCurrencyDecimals, + isBorrowing, + isUsingAsCollateral, +} from "../helpers/contracts-helpers"; +import { + approveTo, + createNewPool, + fund, + mintNewPosition, +} from "./helpers/uniswapv3-helper"; +import {encodeSqrtRatioX96} from "@uniswap/v3-sdk"; +import {waitForTx} from "../helpers/misc-utils"; +import {supplyAndValidate} from "./helpers/validated-steps"; +import {parseEther} from "ethers/lib/utils"; +import {BigNumber} from "ethers"; +import {deployAccountFactory} from "../helpers/contracts-deployments"; + +describe("Account Abstraction Migration", () => { + let variableDebt; + let daiData; + let wethData; + let uniswapData; + + const fixture = async () => { + const testEnv = await loadFixture(testEnvFixture); + + const { + users: [user1, user2], + dai, + pDai, + weth, + pWETH, + nftPositionManager, + pool, + nUniswapV3, + protocolDataProvider, + } = testEnv; + + daiData = await pool.getReserveData(dai.address); + wethData = await pool.getReserveData(weth.address); + uniswapData = await pool.getReserveData(nftPositionManager.address); + + const userDaiAmount = await convertToCurrencyDecimals(dai.address, "10000"); + const userWethAmount = await convertToCurrencyDecimals(weth.address, "10"); + await fund({token: dai, user: user1, amount: userDaiAmount}); + await fund({token: weth, user: user1, amount: userWethAmount}); + const nft = nftPositionManager.connect(user1.signer); + await approveTo({ + target: nftPositionManager.address, + token: dai, + user: user1, + }); + await approveTo({ + target: nftPositionManager.address, + token: weth, + user: user1, + }); + const fee = 3000; + const tickSpacing = fee / 50; + const initialPrice = encodeSqrtRatioX96(1, 1000); + const lowerPrice = encodeSqrtRatioX96(1, 10000); + const upperPrice = encodeSqrtRatioX96(1, 100); + await createNewPool({ + positionManager: nft, + token0: dai, + token1: weth, + fee: fee, + initialSqrtPrice: initialPrice.toString(), + }); + await mintNewPosition({ + nft: nft, + token0: dai, + token1: weth, + fee: fee, + user: user1, + tickSpacing: tickSpacing, + lowerPrice, + upperPrice, + token0Amount: userDaiAmount, + token1Amount: userWethAmount, + }); + expect(await nftPositionManager.balanceOf(user1.address)).to.eq(1); + + await nft.setApprovalForAll(pool.address, true); + + const daiAmount = parseEther("10000"); + const wethAmount = parseEther("100"); + const borrowDaiAmount = parseEther("5000"); + await supplyAndValidate(dai, "10000", user2, true); + await supplyAndValidate(weth, "100", user1, true); + await waitForTx( + await pool + .connect(user1.signer) + .borrow(dai.address, borrowDaiAmount, 0, user1.address) + ); + await waitForTx( + await pool + .connect(user1.signer) + .supplyERC721( + nftPositionManager.address, + [{tokenId: 1, useAsCollateral: true}], + user1.address, + 0, + { + gasLimit: 12_450_000, + } + ) + ); + + expect(await pDai.balanceOf(user2.address)).to.be.closeTo( + daiAmount, + parseEther("1") + ); + expect(await pWETH.balanceOf(user1.address)).to.be.closeTo( + wethAmount, + parseEther("0.01") + ); + expect(await nUniswapV3.balanceOf(user1.address)).to.eq(1); + + const {variableDebtTokenAddress: variableDebtTokenAddress} = + await protocolDataProvider.getReserveTokensAddresses(dai.address); + variableDebt = await getVariableDebtToken(variableDebtTokenAddress); + expect(await variableDebt.balanceOf(user1.address)).to.be.closeTo( + borrowDaiAmount, + parseEther("1") + ); + + return testEnv; + }; + + it("user position migration", async () => { + const testEnv = await loadFixture(fixture); + const { + users: [user1, user2, entryPoint], + pool, + dai, + pDai, + weth, + pWETH, + nUniswapV3, + nftPositionManager, + } = testEnv; + const accountFactory = await deployAccountFactory(entryPoint.address); + + const daiAmount = parseEther("10000"); + const wethAmount = parseEther("100"); + const borrowDaiAmount = parseEther("5000"); + + await accountFactory.createAccount(user1.address, "1"); + let aaAccount = await accountFactory.getAddress(user1.address, "1"); + + await waitForTx( + await pool.connect(user1.signer).positionMoveToAA(aaAccount) + ); + + expect(await pWETH.balanceOf(aaAccount)).to.be.closeTo( + wethAmount, + parseEther("0.01") + ); + expect(await nUniswapV3.balanceOf(aaAccount)).to.eq(1); + expect(await variableDebt.balanceOf(aaAccount)).to.be.closeTo( + borrowDaiAmount, + parseEther("1") + ); + let userConfig = BigNumber.from( + (await pool.getUserConfiguration(aaAccount)).data + ); + expect(isUsingAsCollateral(userConfig, wethData.id)).to.be.true; + expect(isUsingAsCollateral(userConfig, uniswapData.id)).to.be.true; + expect(isUsingAsCollateral(userConfig, daiData.id)).to.be.false; + expect(isBorrowing(userConfig, daiData.id)).to.be.true; + + await accountFactory.createAccount(user2.address, "2"); + aaAccount = await accountFactory.getAddress(user2.address, "2"); + await waitForTx( + await pool.connect(user2.signer).positionMoveToAA(aaAccount) + ); + + expect(await pDai.balanceOf(aaAccount)).to.be.closeTo( + daiAmount, + parseEther("1") + ); + userConfig = BigNumber.from( + (await pool.getUserConfiguration(aaAccount)).data + ); + expect(isUsingAsCollateral(userConfig, wethData.id)).to.be.false; + expect(isUsingAsCollateral(userConfig, uniswapData.id)).to.be.false; + expect(isUsingAsCollateral(userConfig, daiData.id)).to.be.true; + expect(isBorrowing(userConfig, daiData.id)).to.be.false; + + expect(await dai.balanceOf(pDai.address)).to.be.closeTo( + borrowDaiAmount, + parseEther("1") + ); + expect(await weth.balanceOf(pWETH.address)).to.be.closeTo( + wethAmount, + parseEther("0.01") + ); + expect(await nftPositionManager.balanceOf(nUniswapV3.address)).to.be.eq(1); + }); +}); diff --git a/test/_account_abstraction.spec.ts b/test/_account_abstraction.spec.ts index 6e5149d7..e7f52fbd 100644 --- a/test/_account_abstraction.spec.ts +++ b/test/_account_abstraction.spec.ts @@ -4,9 +4,12 @@ import {loadFixture} from "@nomicfoundation/hardhat-network-helpers"; import { deployAccount, deployAccountFactory, - deployAccountRegistry, } from "../helpers/contracts-deployments"; -import {getAccount, getChainId} from "../helpers/contracts-getters"; +import { + getAccount, + getAccountRegistry, + getChainId, +} from "../helpers/contracts-getters"; import {expect} from "chai"; import {calcOpHash, waitForTx} from "../helpers/misc-utils"; import {AccountProxy__factory} from "../types"; @@ -16,10 +19,8 @@ const fixture = async () => { const { users: [, entryPoint], } = testEnv; - const accountImpl = await deployAccount(entryPoint.address); - const accountRegistry = await deployAccountRegistry(accountImpl.address); - console.log("latest impl", await accountRegistry.getLatestImplementation()); - const accountFactory = await deployAccountFactory(accountRegistry.address); + const accountFactory = await deployAccountFactory(entryPoint.address); + const accountRegistry = await getAccountRegistry(); return {...testEnv, accountFactory, accountRegistry}; };