From eb89afb3a9625cc88e77bd8ee4214e6f5aecee32 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Tue, 24 Oct 2023 16:04:18 +0800 Subject: [PATCH 1/5] Layer2 AA Migration (#406) * chore: AA migration * chore: small fix * chore: gas optimization * chore: refactor for deploy * chore: fix lint * chore: fix * chore: support user migration * chore: fix format * chore: fix script and format * chore: some optimization * chore: remove batch migration * chore: update script * chore: use memory struct to cache local variable * chore: fix lint * chore: remove default parameter --- Makefile | 4 + contracts/interfaces/IAccountFactory.sol | 9 + contracts/interfaces/INToken.sol | 3 +- contracts/interfaces/IPool.sol | 4 +- contracts/interfaces/IPoolAAPositionMover.sol | 13 ++ .../protocol/libraries/helpers/Errors.sol | 1 + .../protocol/pool/PoolAAPositionMove.sol | 209 ++++++++++++++++++ helpers/contracts-deployments.ts | 42 +++- helpers/contracts-getters.ts | 12 + helpers/hardhat-constants.ts | 3 + helpers/types.ts | 1 + scripts/deployments/steps/06_pool.ts | 19 ++ .../steps/24_accountAbstraction.ts | 21 +- scripts/upgrade/pool.ts | 26 +++ tasks/upgrade/index.ts | 15 ++ test/_aa_migration.spec.ts | 206 +++++++++++++++++ test/_account_abstraction.spec.ts | 13 +- 17 files changed, 571 insertions(+), 30 deletions(-) create mode 100644 contracts/interfaces/IAccountFactory.sol create mode 100644 contracts/interfaces/IPoolAAPositionMover.sol create mode 100644 contracts/protocol/pool/PoolAAPositionMove.sol create mode 100644 test/_aa_migration.spec.ts diff --git a/Makefile b/Makefile index eb039b607..1b4d5beba 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 000000000..195d97b98 --- /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 b61282bea..552fbc152 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 0839b4c3a..f099ff026 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 000000000..f8ad261a4 --- /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 c10efa7b1..ece1ab752 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 000000000..7e290e2a9 --- /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 25a9ac4ff..ebce79b16 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 033947d2c..be45e8662 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 30dd2c731..e0637a21e 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 309d65b2f..3b91198bb 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 3348f9d8b..2776c069e 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 4bca47b5c..46399bdba 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 ac149d4b0..0166a2106 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 fda8cb44f..bb192094d 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 000000000..90cb8da39 --- /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 6e5149d78..e7f52fbd2 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}; }; From 763902c68d2ec7b2627ac6fd5c5e7df7cd2cbe91 Mon Sep 17 00:00:00 2001 From: Cheng JIANG Date: Wed, 1 Nov 2023 19:46:54 +0800 Subject: [PATCH 2/5] Feat/improve position mover (#422) * feat: add to support for moving bendao position Signed-off-by: GopherJ * fix: tests Signed-off-by: GopherJ * fix: the import path of ScaledBalanceTokenBaseERC20 --------- Signed-off-by: GopherJ Co-authored-by: jfzhou5 <1241330802@qq.com> --- contracts/interfaces/IPoolPositionMover.sol | 5 ++- .../libraries/logic/PositionMoverLogic.sol | 23 ++++++++----- contracts/protocol/pool/PoolPositionMover.sol | 6 ++-- .../protocol/tokenization/PTokenSApe.sol | 2 +- .../tokenization/RebasingDebtToken.sol | 2 +- .../protocol/tokenization/RebasingPToken.sol | 2 +- test/_pool_position_mover.spec.ts | 32 ++++++++++++------- 7 files changed, 47 insertions(+), 25 deletions(-) diff --git a/contracts/interfaces/IPoolPositionMover.sol b/contracts/interfaces/IPoolPositionMover.sol index 768fb77b0..e5171d88e 100644 --- a/contracts/interfaces/IPoolPositionMover.sol +++ b/contracts/interfaces/IPoolPositionMover.sol @@ -10,7 +10,10 @@ import {ApeCoinStaking} from "../dependencies/yoga-labs/ApeCoinStaking.sol"; * @notice Defines the basic interface for an ParaSpace Pool. **/ interface IPoolPositionMover { - function movePositionFromBendDAO(uint256[] calldata loanIds) external; + function movePositionFromBendDAO( + uint256[] calldata loanIds, + address to + ) external; //# Migration step // diff --git a/contracts/protocol/libraries/logic/PositionMoverLogic.sol b/contracts/protocol/libraries/logic/PositionMoverLogic.sol index d73f39491..c3d33017c 100644 --- a/contracts/protocol/libraries/logic/PositionMoverLogic.sol +++ b/contracts/protocol/libraries/logic/PositionMoverLogic.sol @@ -76,7 +76,8 @@ library PositionMoverLogic { IPoolAddressesProvider poolAddressProvider, ILendPoolLoan lendPoolLoan, ILendPool lendPool, - uint256[] calldata loandIds + uint256[] calldata loandIds, + address to ) external { BendDAOPositionMoverVars memory tmpVar; @@ -97,7 +98,7 @@ library PositionMoverLogic { loandIds[index] ); - supplyNFTandBorrowWETH(ps, poolAddressProvider, tmpVar); + supplyNFTandBorrowWETH(ps, poolAddressProvider, tmpVar, to); emit PositionMovedFromBendDAO( tmpVar.nftAsset, @@ -143,8 +144,14 @@ library PositionMoverLogic { function supplyNFTandBorrowWETH( DataTypes.PoolStorage storage ps, IPoolAddressesProvider poolAddressProvider, - BendDAOPositionMoverVars memory tmpVar + BendDAOPositionMoverVars memory tmpVar, + address to ) internal { + require( + to == msg.sender || IAccount(to).owner() == msg.sender, + Errors.NOT_THE_OWNER + ); + DataTypes.ERC721SupplyParams[] memory tokenData = new DataTypes.ERC721SupplyParams[](1); tokenData[0] = DataTypes.ERC721SupplyParams({ @@ -154,11 +161,11 @@ library PositionMoverLogic { SupplyLogic.executeSupplyERC721( ps._reserves, - ps._usersConfig[msg.sender], + ps._usersConfig[to], DataTypes.ExecuteSupplyERC721Params({ asset: tmpVar.nftAsset, tokenData: tokenData, - onBehalfOf: msg.sender, + onBehalfOf: to, payer: msg.sender, referralCode: 0x0 }) @@ -167,11 +174,11 @@ library PositionMoverLogic { BorrowLogic.executeBorrow( ps._reserves, ps._reservesList, - ps._usersConfig[msg.sender], + ps._usersConfig[to], DataTypes.ExecuteBorrowParams({ asset: tmpVar.weth, - user: msg.sender, - onBehalfOf: msg.sender, + user: to, + onBehalfOf: to, amount: tmpVar.borrowAmount, referralCode: 0x0, releaseUnderlying: false, diff --git a/contracts/protocol/pool/PoolPositionMover.sol b/contracts/protocol/pool/PoolPositionMover.sol index 6f81ee4bd..4cbfbe0f4 100644 --- a/contracts/protocol/pool/PoolPositionMover.sol +++ b/contracts/protocol/pool/PoolPositionMover.sol @@ -74,7 +74,8 @@ contract PoolPositionMover is } function movePositionFromBendDAO( - uint256[] calldata loanIds + uint256[] calldata loanIds, + address to ) external nonReentrant { DataTypes.PoolStorage storage ps = poolStorage(); @@ -83,7 +84,8 @@ contract PoolPositionMover is ADDRESSES_PROVIDER, BENDDAO_LEND_POOL_LOAN, BENDDAO_LEND_POOL, - loanIds + loanIds, + to ); } diff --git a/contracts/protocol/tokenization/PTokenSApe.sol b/contracts/protocol/tokenization/PTokenSApe.sol index 54eacf18e..525f3fcb1 100644 --- a/contracts/protocol/tokenization/PTokenSApe.sol +++ b/contracts/protocol/tokenization/PTokenSApe.sol @@ -13,7 +13,7 @@ import {IERC20} from "../../dependencies/openzeppelin/contracts/IERC20.sol"; import {IScaledBalanceToken} from "../../interfaces/IScaledBalanceToken.sol"; import {IncentivizedERC20} from "./base/IncentivizedERC20.sol"; import {DataTypes} from "../libraries/types/DataTypes.sol"; -import {ScaledBalanceTokenBaseERC20} from "contracts/protocol/tokenization/base/ScaledBalanceTokenBaseERC20.sol"; +import {ScaledBalanceTokenBaseERC20} from "../../protocol/tokenization/base/ScaledBalanceTokenBaseERC20.sol"; /** * @title sApe PToken diff --git a/contracts/protocol/tokenization/RebasingDebtToken.sol b/contracts/protocol/tokenization/RebasingDebtToken.sol index da38850ff..ae1adbe88 100644 --- a/contracts/protocol/tokenization/RebasingDebtToken.sol +++ b/contracts/protocol/tokenization/RebasingDebtToken.sol @@ -7,7 +7,7 @@ import {WadRayMath} from "../libraries/math/WadRayMath.sol"; import {Errors} from "../libraries/helpers/Errors.sol"; import {SafeCast} from "../../dependencies/openzeppelin/contracts/SafeCast.sol"; import {IScaledBalanceToken} from "../../interfaces/IScaledBalanceToken.sol"; -import {ScaledBalanceTokenBaseERC20} from "contracts/protocol/tokenization/base/ScaledBalanceTokenBaseERC20.sol"; +import {ScaledBalanceTokenBaseERC20} from "../../protocol/tokenization/base/ScaledBalanceTokenBaseERC20.sol"; /** * @title Rebasing Debt Token diff --git a/contracts/protocol/tokenization/RebasingPToken.sol b/contracts/protocol/tokenization/RebasingPToken.sol index aeb820321..02ad0b4ec 100644 --- a/contracts/protocol/tokenization/RebasingPToken.sol +++ b/contracts/protocol/tokenization/RebasingPToken.sol @@ -10,7 +10,7 @@ import {Errors} from "../libraries/helpers/Errors.sol"; import {IERC20} from "../../dependencies/openzeppelin/contracts/IERC20.sol"; import {GPv2SafeERC20} from "../../dependencies/gnosis/contracts/GPv2SafeERC20.sol"; import {IScaledBalanceToken} from "../../interfaces/IScaledBalanceToken.sol"; -import {ScaledBalanceTokenBaseERC20} from "contracts/protocol/tokenization/base/ScaledBalanceTokenBaseERC20.sol"; +import {ScaledBalanceTokenBaseERC20} from "../../protocol/tokenization/base/ScaledBalanceTokenBaseERC20.sol"; /** * @title Rebasing PToken diff --git a/test/_pool_position_mover.spec.ts b/test/_pool_position_mover.spec.ts index bcb621459..43f1f1e1e 100644 --- a/test/_pool_position_mover.spec.ts +++ b/test/_pool_position_mover.spec.ts @@ -67,8 +67,9 @@ describe("Pool: rescue tokens", () => { 2 ); - await expect(pool.connect(user1.signer).movePositionFromBendDAO([1])).to.be - .reverted; + await expect( + pool.connect(user1.signer).movePositionFromBendDAO([1], user1.address) + ).to.be.reverted; }); it("moving position should succeed for an active loan", async () => { @@ -81,7 +82,11 @@ describe("Pool: rescue tokens", () => { } = testEnv; await supplyAndValidate(weth, "20000000000", user1, true); - await expect(await pool.connect(user1.signer).movePositionFromBendDAO([1])); + await expect( + await pool + .connect(user1.signer) + .movePositionFromBendDAO([1], user1.address) + ); await expect(await variableDebtWeth.balanceOf(user1.address)).to.be.eq( "200000" @@ -96,7 +101,7 @@ describe("Pool: rescue tokens", () => { } = testEnv; await expect( - pool.connect(user1.signer).movePositionFromBendDAO([1]) + pool.connect(user1.signer).movePositionFromBendDAO([1], user1.address) ).to.be.revertedWith("Loan not active"); }); @@ -119,8 +124,9 @@ describe("Pool: rescue tokens", () => { const agg = await getAggregator(undefined, await bayc.symbol()); await agg.updateLatestAnswer("100000"); - await expect(pool.connect(user2.signer).movePositionFromBendDAO([2])).to.be - .reverted; + await expect( + pool.connect(user2.signer).movePositionFromBendDAO([2], user2.address) + ).to.be.reverted; await changePriceAndValidate(bayc, "50"); }); @@ -141,8 +147,9 @@ describe("Pool: rescue tokens", () => { 2 ); - await expect(pool.connect(user2.signer).movePositionFromBendDAO([3])).to.be - .reverted; + await expect( + pool.connect(user2.signer).movePositionFromBendDAO([3], user2.address) + ).to.be.reverted; }); it("moving position should fail if the asset is not supported", async () => { @@ -174,8 +181,9 @@ describe("Pool: rescue tokens", () => { 2 ); - await expect(pool.connect(user1.signer).movePositionFromBendDAO([4])).to.be - .reverted; + await expect( + pool.connect(user1.signer).movePositionFromBendDAO([4], user1.address) + ).to.be.reverted; }); it("moving multiple positions should succeed for active loans", async () => { @@ -196,7 +204,9 @@ describe("Pool: rescue tokens", () => { ); await expect( - await pool.connect(user1.signer).movePositionFromBendDAO([3, 4]) + await pool + .connect(user1.signer) + .movePositionFromBendDAO([3, 4], user1.address) ); await expect(await variableDebtWeth.balanceOf(user1.address)).to.be.eq( From 9d585ec92e586679ab58232365d8189247e8d23a Mon Sep 17 00:00:00 2001 From: zhouJF <58616306+jfzhou5@users.noreply.github.com> Date: Mon, 13 Nov 2023 13:11:58 +0800 Subject: [PATCH 3/5] feat: support uBAYC and uPPG (#426) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add uBAYC and uPPG on dev v2 * update µtoken symbol * chore: replace illegal char * chore: fix lint --------- Co-authored-by: zhoujia6139 --- Makefile | 2 +- helpers/types.ts | 6 +++++ market-config/index.ts | 8 +++++++ market-config/mocks.ts | 4 ++++ market-config/reservesConfigs.ts | 34 +++++++++++++++++++++++++++++ market-config/timeLockStrategies.ts | 24 ++++++++++++++++++++ 6 files changed, 77 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 1b4d5beba..f3e085f8d 100644 --- a/Makefile +++ b/Makefile @@ -354,7 +354,7 @@ deploy-allAggregators: .PHONY: deploy-allReserves deploy-allReserves: - make TASK_NAME=deploy:all-allReserves run-task + make TASK_NAME=deploy:all-reserves run-task .PHONY: deploy-uiIncentiveDataProvider deploy-uiIncentiveDataProvider: diff --git a/helpers/types.ts b/helpers/types.ts index 3b91198bb..5e6c1842e 100644 --- a/helpers/types.ts +++ b/helpers/types.ts @@ -509,6 +509,8 @@ export interface iAssetBase { KODA: T; BLOCKS: T; EXRP: T; + uBAYC: T; + uPPG: T; } export type iAssetsWithoutETH = Omit, "ETH">; @@ -574,6 +576,8 @@ export type iParaSpacePoolAssets = Pick< | "KODA" | "BLOCKS" | "EXRP" + | "uBAYC" + | "uPPG" >; export type iMultiPoolsAssets = iAssetCommon | iParaSpacePoolAssets; @@ -620,6 +624,8 @@ export enum ERC20TokenContractId { stMATIC = "stMATIC", CRV = "CRV", WMATIC = "WMATIC", + uBAYC = "uBAYC", + uPPG = "uPPG", } export enum ERC721TokenContractId { diff --git a/market-config/index.ts b/market-config/index.ts index 69bb652ac..06ee039fa 100644 --- a/market-config/index.ts +++ b/market-config/index.ts @@ -76,6 +76,8 @@ import { strategyWBTCWH, strategySTDOT, strategyEXRP, + strategyuBAYC, + strategyuPPG, } from "./reservesConfigs"; export const CommonConfig: Pick< @@ -381,6 +383,8 @@ export const GoerliConfig: IParaSpaceConfiguration = { VSL: strategyVSL, KODA: strategyKODA, BLOCKS: strategyBLOCKS, + uBAYC: strategyuBAYC, + uPPG: strategyuPPG, }, DelegationRegistry: "0x00000000000076A84feF008CDAbe6409d2FE638B", }; @@ -892,6 +896,8 @@ export const MainnetConfig: IParaSpaceConfiguration = { VSL: "0x5b1085136a811e55b2bb2ca1ea456ba82126a376", KODA: "0xe012baf811cf9c05c408e879c399960d1f305903", BLOCKS: "0x059EDD72Cd353dF5106D2B9cC5ab83a52287aC3a", + uBAYC: "0x1e610de0d7acfa1d820024948a91d96c5c9ce6b9", + uPPG: "0x30F7c830e0C2f4bEC871DF809D73E27eF19EB151", }, YogaLabs: { ApeCoinStaking: "0x5954aB967Bc958940b7EB73ee84797Dc8a2AFbb9", @@ -985,6 +991,8 @@ export const MainnetConfig: IParaSpaceConfiguration = { VSL: strategyVSL, KODA: strategyKODA, BLOCKS: strategyBLOCKS, + uBAYC: strategyuBAYC, + uPPG: strategyuPPG, }, Mocks: undefined, Oracle: MainnetOracleConfig, diff --git a/market-config/mocks.ts b/market-config/mocks.ts index 6f937681f..6d4fdc362 100644 --- a/market-config/mocks.ts +++ b/market-config/mocks.ts @@ -64,6 +64,8 @@ export const MOCK_CHAINLINK_AGGREGATORS_PRICES = { KODA: parseEther("9.5").toString(), BLOCKS: parseEther("9.54").toString(), EXRP: parseEther("0.2").toString(), + uBAYC: parseEther("0.000002768").toString(), + uPPG: parseEther("0.000000477").toString(), }; export const MOCK_CHAINLINK_AGGREGATORS_USD_PRICES = { @@ -128,6 +130,8 @@ export const MOCK_CHAINLINK_AGGREGATORS_USD_PRICES = { KODA: parseUnits("14154", 8).toString(), BLOCKS: parseUnits("10800", 8).toString(), EXRP: parseUnits("170", 8).toString(), + uBAYC: parseUnits("0.005", 8).toString(), + uPPG: parseUnits("0.004", 8).toString(), }; export const MOCK_TOKEN_MINT_VALUE = { diff --git a/market-config/reservesConfigs.ts b/market-config/reservesConfigs.ts index e66535b74..70c0ea388 100644 --- a/market-config/reservesConfigs.ts +++ b/market-config/reservesConfigs.ts @@ -112,6 +112,8 @@ import { timeLockStrategyWBTCWH, timeLockStrategySTDOT, timeLockStrategyEXRP, + timeLockStrategyuPPG, + timeLockStrategyuBAYC, } from "./timeLockStrategies"; export const strategyDAI: IReserveParams = { @@ -1077,3 +1079,35 @@ export const strategyEXRP: IReserveParams = { borrowCap: "0", supplyCap: "1000", }; + +export const strategyuBAYC: IReserveParams = { + strategy: rateStrategyBLUR, + auctionStrategy: auctionStrategyZero, + timeLockStrategy: timeLockStrategyuBAYC, + baseLTVAsCollateral: "4000", + liquidationThreshold: "8000", + liquidationProtocolFeePercentage: "0", + liquidationBonus: "10500", + borrowingEnabled: true, + reserveDecimals: "18", + xTokenImpl: eContractid.PTokenImpl, + reserveFactor: "1000", + borrowCap: "0", + supplyCap: "0", +}; + +export const strategyuPPG: IReserveParams = { + strategy: rateStrategyBLUR, + auctionStrategy: auctionStrategyZero, + timeLockStrategy: timeLockStrategyuPPG, + baseLTVAsCollateral: "3000", + liquidationThreshold: "6500", + liquidationProtocolFeePercentage: "0", + liquidationBonus: "10500", + borrowingEnabled: true, + reserveDecimals: "18", + xTokenImpl: eContractid.PTokenImpl, + reserveFactor: "1000", + borrowCap: "0", + supplyCap: "0", +}; diff --git a/market-config/timeLockStrategies.ts b/market-config/timeLockStrategies.ts index 5fc4d246a..14fa32282 100644 --- a/market-config/timeLockStrategies.ts +++ b/market-config/timeLockStrategies.ts @@ -61,6 +61,30 @@ export const timeLockStrategyWETH: ITimeLockStrategyParams = { period: "86400", }; +export const timeLockStrategyuBAYC: ITimeLockStrategyParams = { + name: "timeLockStrategyuBAYC", + minThreshold: parseUnits("1800000", 18).toString(), + midThreshold: parseUnits("5400000", 18).toString(), + minWaitTime: "12", + midWaitTime: "7200", + maxWaitTime: "21600", + poolPeriodWaitTime: "600", + poolPeriodLimit: parseUnits("64800000", 18).toString(), + period: "86400", +}; + +export const timeLockStrategyuPPG: ITimeLockStrategyParams = { + name: "timeLockStrategyuPPG", + minThreshold: parseUnits("10000000", 18).toString(), + midThreshold: parseUnits("30000000", 18).toString(), + minWaitTime: "12", + midWaitTime: "7200", + maxWaitTime: "21600", + poolPeriodWaitTime: "600", + poolPeriodLimit: parseUnits("360000000", 18).toString(), + period: "86400", +}; + export const timeLockStrategyCBETH: ITimeLockStrategyParams = { name: "timeLockStrategyCBETH", minThreshold: parseUnits("51.5", 18).toString(), From e9c926fb22c5b1d2e90fa289929481f3ee39e693 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Tue, 14 Nov 2023 09:29:08 +0800 Subject: [PATCH 4/5] Support delegation registry v2 (#425) * chore: support delegation registry v2 * chore: fix ui contract and add placeholder slot * chore: add slot gap * chore: add link libraray debug info and support upgrade ntoken by symbol * chore: revert adhoc change * feat: add renew buffered txs Signed-off-by: GopherJ --------- Signed-off-by: GopherJ Co-authored-by: GopherJ --- Makefile | 4 + contracts/account-abstraction/Account.sol | 2 +- .../account-abstraction/AccountFactory.sol | 2 +- .../account-abstraction/AccountProxy.sol | 2 +- .../account-abstraction/AccountRegistry.sol | 2 +- .../callback/TokenCallbackHandler.sol | 2 +- .../delegation/DelegateRegistry.sol | 472 ++++++++++++++++++ .../delegation/DelegationRegistry.sol | 441 ---------------- .../delegation/IDelegateRegistry.sol | 221 ++++++++ .../delegation/libraries/RegistryHashes.sol | 303 +++++++++++ .../delegation/libraries/RegistryOps.sol | 31 ++ .../delegation/libraries/RegistryStorage.sol | 69 +++ contracts/protocol/tokenization/NToken.sol | 1 - .../base/MintableIncentivizedERC721.sol | 5 + .../libraries/MintableERC721Logic.sol | 8 +- contracts/ui/UiPoolDataProvider.sol | 33 +- .../ui/interfaces/IUiPoolDataProvider.sol | 7 +- helpers/contracts-deployments.ts | 6 +- helpers/contracts-getters.ts | 4 +- helpers/contracts-helpers.ts | 13 + helpers/hardhat-constants.ts | 5 +- market-config/index.ts | 4 +- scripts/upgrade/ntoken.ts | 7 + tasks/dev/timeLock.ts | 22 + test/_xtoken_ntoken_delegation.ts | 69 +-- typos.toml | 7 +- 26 files changed, 1219 insertions(+), 523 deletions(-) create mode 100644 contracts/dependencies/delegation/DelegateRegistry.sol delete mode 100644 contracts/dependencies/delegation/DelegationRegistry.sol create mode 100644 contracts/dependencies/delegation/IDelegateRegistry.sol create mode 100644 contracts/dependencies/delegation/libraries/RegistryHashes.sol create mode 100644 contracts/dependencies/delegation/libraries/RegistryOps.sol create mode 100644 contracts/dependencies/delegation/libraries/RegistryStorage.sol diff --git a/Makefile b/Makefile index f3e085f8d..857afca13 100644 --- a/Makefile +++ b/Makefile @@ -524,6 +524,10 @@ decode-queued-txs: list-buffered-txs: make TASK_NAME=list-buffered-txs run-task +.PHONY: renew-buffered-txs +renew-buffered-txs: + make TASK_NAME=renew-buffered-txs run-task + .PHONY: decode-buffered-txs decode-buffered-txs: make TASK_NAME=decode-buffered-txs run-task diff --git a/contracts/account-abstraction/Account.sol b/contracts/account-abstraction/Account.sol index d738754c9..ccd0a10b0 100644 --- a/contracts/account-abstraction/Account.sol +++ b/contracts/account-abstraction/Account.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity 0.8.17; +pragma solidity ^0.8.17; import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; diff --git a/contracts/account-abstraction/AccountFactory.sol b/contracts/account-abstraction/AccountFactory.sol index 23ce7a379..b8cafecef 100644 --- a/contracts/account-abstraction/AccountFactory.sol +++ b/contracts/account-abstraction/AccountFactory.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity 0.8.17; +pragma solidity ^0.8.17; import "@openzeppelin/contracts/utils/Create2.sol"; import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; diff --git a/contracts/account-abstraction/AccountProxy.sol b/contracts/account-abstraction/AccountProxy.sol index 892db7ff5..c36b89e49 100644 --- a/contracts/account-abstraction/AccountProxy.sol +++ b/contracts/account-abstraction/AccountProxy.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity 0.8.17; +pragma solidity ^0.8.17; import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import "./AccountRegistry.sol"; diff --git a/contracts/account-abstraction/AccountRegistry.sol b/contracts/account-abstraction/AccountRegistry.sol index 3979dc91a..2fb19feb0 100644 --- a/contracts/account-abstraction/AccountRegistry.sol +++ b/contracts/account-abstraction/AccountRegistry.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity 0.8.17; +pragma solidity ^0.8.17; import {Ownable} from "../dependencies/openzeppelin/contracts/Ownable.sol"; import {Errors} from "../protocol/libraries/helpers/Errors.sol"; diff --git a/contracts/account-abstraction/callback/TokenCallbackHandler.sol b/contracts/account-abstraction/callback/TokenCallbackHandler.sol index 2bf1edd5f..e8970127f 100644 --- a/contracts/account-abstraction/callback/TokenCallbackHandler.sol +++ b/contracts/account-abstraction/callback/TokenCallbackHandler.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity 0.8.17; +pragma solidity ^0.8.17; /* solhint-disable no-empty-blocks */ diff --git a/contracts/dependencies/delegation/DelegateRegistry.sol b/contracts/dependencies/delegation/DelegateRegistry.sol new file mode 100644 index 000000000..8cf0a0032 --- /dev/null +++ b/contracts/dependencies/delegation/DelegateRegistry.sol @@ -0,0 +1,472 @@ +// SPDX-License-Identifier: CC0-1.0 +pragma solidity ^0.8.21; + +import {IDelegateRegistry as IDelegateRegistry} from "./IDelegateRegistry.sol"; +import {RegistryHashes as Hashes} from "./libraries/RegistryHashes.sol"; +import {RegistryStorage as Storage} from "./libraries/RegistryStorage.sol"; +import {RegistryOps as Ops} from "./libraries/RegistryOps.sol"; + +/** + * @title DelegateRegistry + * @custom:version 2.0 + * @custom:coauthor foobar (0xfoobar) + * @custom:coauthor mireynolds + * @notice A standalone immutable registry storing delegated permissions from one address to another + */ +contract DelegateRegistry is IDelegateRegistry { + /// @dev Only this mapping should be used to verify delegations; the other mapping arrays are for enumerations + mapping(bytes32 delegationHash => bytes32[5] delegationStorage) internal delegations; + + /// @dev Vault delegation enumeration outbox, for pushing new hashes only + mapping(address from => bytes32[] delegationHashes) internal outgoingDelegationHashes; + + /// @dev Delegate enumeration inbox, for pushing new hashes only + mapping(address to => bytes32[] delegationHashes) internal incomingDelegationHashes; + + /** + * ----------- WRITE ----------- + */ + + /// @inheritdoc IDelegateRegistry + function multicall(bytes[] calldata data) external payable override returns (bytes[] memory results) { + results = new bytes[](data.length); + bool success; + unchecked { + for (uint256 i = 0; i < data.length; ++i) { + //slither-disable-next-line calls-loop,delegatecall-loop + (success, results[i]) = address(this).delegatecall(data[i]); + if (!success) revert MulticallFailed(); + } + } + } + + /// @inheritdoc IDelegateRegistry + function delegateAll(address to, bytes32 rights, bool enable) external payable override returns (bytes32 hash) { + hash = Hashes.allHash(msg.sender, rights, to); + bytes32 location = Hashes.location(hash); + address loadedFrom = _loadFrom(location); + if (enable) { + if (loadedFrom == Storage.DELEGATION_EMPTY) { + _pushDelegationHashes(msg.sender, to, hash); + _writeDelegationAddresses(location, msg.sender, to, address(0)); + if (rights != "") _writeDelegation(location, Storage.POSITIONS_RIGHTS, rights); + } else if (loadedFrom == Storage.DELEGATION_REVOKED) { + _updateFrom(location, msg.sender); + } + } else if (loadedFrom == msg.sender) { + _updateFrom(location, Storage.DELEGATION_REVOKED); + } + emit DelegateAll(msg.sender, to, rights, enable); + } + + /// @inheritdoc IDelegateRegistry + function delegateContract(address to, address contract_, bytes32 rights, bool enable) external payable override returns (bytes32 hash) { + hash = Hashes.contractHash(msg.sender, rights, to, contract_); + bytes32 location = Hashes.location(hash); + address loadedFrom = _loadFrom(location); + if (enable) { + if (loadedFrom == Storage.DELEGATION_EMPTY) { + _pushDelegationHashes(msg.sender, to, hash); + _writeDelegationAddresses(location, msg.sender, to, contract_); + if (rights != "") _writeDelegation(location, Storage.POSITIONS_RIGHTS, rights); + } else if (loadedFrom == Storage.DELEGATION_REVOKED) { + _updateFrom(location, msg.sender); + } + } else if (loadedFrom == msg.sender) { + _updateFrom(location, Storage.DELEGATION_REVOKED); + } + emit DelegateContract(msg.sender, to, contract_, rights, enable); + } + + /// @inheritdoc IDelegateRegistry + function delegateERC721(address to, address contract_, uint256 tokenId, bytes32 rights, bool enable) external payable override returns (bytes32 hash) { + hash = Hashes.erc721Hash(msg.sender, rights, to, tokenId, contract_); + bytes32 location = Hashes.location(hash); + address loadedFrom = _loadFrom(location); + if (enable) { + if (loadedFrom == Storage.DELEGATION_EMPTY) { + _pushDelegationHashes(msg.sender, to, hash); + _writeDelegationAddresses(location, msg.sender, to, contract_); + _writeDelegation(location, Storage.POSITIONS_TOKEN_ID, tokenId); + if (rights != "") _writeDelegation(location, Storage.POSITIONS_RIGHTS, rights); + } else if (loadedFrom == Storage.DELEGATION_REVOKED) { + _updateFrom(location, msg.sender); + } + } else if (loadedFrom == msg.sender) { + _updateFrom(location, Storage.DELEGATION_REVOKED); + } + emit DelegateERC721(msg.sender, to, contract_, tokenId, rights, enable); + } + + // @inheritdoc IDelegateRegistry + function delegateERC20(address to, address contract_, bytes32 rights, uint256 amount) external payable override returns (bytes32 hash) { + hash = Hashes.erc20Hash(msg.sender, rights, to, contract_); + bytes32 location = Hashes.location(hash); + address loadedFrom = _loadFrom(location); + if (amount != 0) { + if (loadedFrom == Storage.DELEGATION_EMPTY) { + _pushDelegationHashes(msg.sender, to, hash); + _writeDelegationAddresses(location, msg.sender, to, contract_); + _writeDelegation(location, Storage.POSITIONS_AMOUNT, amount); + if (rights != "") _writeDelegation(location, Storage.POSITIONS_RIGHTS, rights); + } else if (loadedFrom == Storage.DELEGATION_REVOKED) { + _updateFrom(location, msg.sender); + _writeDelegation(location, Storage.POSITIONS_AMOUNT, amount); + } else if (loadedFrom == msg.sender) { + _writeDelegation(location, Storage.POSITIONS_AMOUNT, amount); + } + } else if (loadedFrom == msg.sender) { + _updateFrom(location, Storage.DELEGATION_REVOKED); + _writeDelegation(location, Storage.POSITIONS_AMOUNT, uint256(0)); + } + emit DelegateERC20(msg.sender, to, contract_, rights, amount); + } + + /// @inheritdoc IDelegateRegistry + function delegateERC1155(address to, address contract_, uint256 tokenId, bytes32 rights, uint256 amount) external payable override returns (bytes32 hash) { + hash = Hashes.erc1155Hash(msg.sender, rights, to, tokenId, contract_); + bytes32 location = Hashes.location(hash); + address loadedFrom = _loadFrom(location); + if (amount != 0) { + if (loadedFrom == Storage.DELEGATION_EMPTY) { + _pushDelegationHashes(msg.sender, to, hash); + _writeDelegationAddresses(location, msg.sender, to, contract_); + _writeDelegation(location, Storage.POSITIONS_TOKEN_ID, tokenId); + _writeDelegation(location, Storage.POSITIONS_AMOUNT, amount); + if (rights != "") _writeDelegation(location, Storage.POSITIONS_RIGHTS, rights); + } else if (loadedFrom == Storage.DELEGATION_REVOKED) { + _updateFrom(location, msg.sender); + _writeDelegation(location, Storage.POSITIONS_AMOUNT, amount); + } else if (loadedFrom == msg.sender) { + _writeDelegation(location, Storage.POSITIONS_AMOUNT, amount); + } + } else if (loadedFrom == msg.sender) { + _updateFrom(location, Storage.DELEGATION_REVOKED); + _writeDelegation(location, Storage.POSITIONS_AMOUNT, uint256(0)); + } + emit DelegateERC1155(msg.sender, to, contract_, tokenId, rights, amount); + } + + /// @dev Transfer native token out + function sweep() external { + assembly ("memory-safe") { + // This hardcoded address is a CREATE2 factory counterfactual smart contract wallet that will always accept native token transfers + let result := call(gas(), 0x000000dE1E80ea5a234FB5488fee2584251BC7e8, selfbalance(), 0, 0, 0, 0) + } + } + + /** + * ----------- CHECKS ----------- + */ + + /// @inheritdoc IDelegateRegistry + function checkDelegateForAll(address to, address from, bytes32 rights) external view override returns (bool valid) { + if (!_invalidFrom(from)) { + valid = _validateFrom(Hashes.allLocation(from, "", to), from); + if (!Ops.or(rights == "", valid)) valid = _validateFrom(Hashes.allLocation(from, rights, to), from); + } + assembly ("memory-safe") { + // Only first 32 bytes of scratch space is accessed + mstore(0, iszero(iszero(valid))) // Compiler cleans dirty booleans on the stack to 1, so do the same here + return(0, 32) // Direct return, skips Solidity's redundant copying to save gas + } + } + + /// @inheritdoc IDelegateRegistry + function checkDelegateForContract(address to, address from, address contract_, bytes32 rights) external view override returns (bool valid) { + if (!_invalidFrom(from)) { + valid = _validateFrom(Hashes.allLocation(from, "", to), from) || _validateFrom(Hashes.contractLocation(from, "", to, contract_), from); + if (!Ops.or(rights == "", valid)) { + valid = _validateFrom(Hashes.allLocation(from, rights, to), from) || _validateFrom(Hashes.contractLocation(from, rights, to, contract_), from); + } + } + assembly ("memory-safe") { + // Only first 32 bytes of scratch space is accessed + mstore(0, iszero(iszero(valid))) // Compiler cleans dirty booleans on the stack to 1, so do the same here + return(0, 32) // Direct return, skips Solidity's redundant copying to save gas + } + } + + /// @inheritdoc IDelegateRegistry + function checkDelegateForERC721(address to, address from, address contract_, uint256 tokenId, bytes32 rights) external view override returns (bool valid) { + if (!_invalidFrom(from)) { + valid = _validateFrom(Hashes.allLocation(from, "", to), from) || _validateFrom(Hashes.contractLocation(from, "", to, contract_), from) + || _validateFrom(Hashes.erc721Location(from, "", to, tokenId, contract_), from); + if (!Ops.or(rights == "", valid)) { + valid = _validateFrom(Hashes.allLocation(from, rights, to), from) || _validateFrom(Hashes.contractLocation(from, rights, to, contract_), from) + || _validateFrom(Hashes.erc721Location(from, rights, to, tokenId, contract_), from); + } + } + assembly ("memory-safe") { + // Only first 32 bytes of scratch space is accessed + mstore(0, iszero(iszero(valid))) // Compiler cleans dirty booleans on the stack to 1, so do the same here + return(0, 32) // Direct return, skips Solidity's redundant copying to save gas + } + } + + /// @inheritdoc IDelegateRegistry + function checkDelegateForERC20(address to, address from, address contract_, bytes32 rights) external view override returns (uint256 amount) { + if (!_invalidFrom(from)) { + amount = (_validateFrom(Hashes.allLocation(from, "", to), from) || _validateFrom(Hashes.contractLocation(from, "", to, contract_), from)) + ? type(uint256).max + : _loadDelegationUint(Hashes.erc20Location(from, "", to, contract_), Storage.POSITIONS_AMOUNT); + if (!Ops.or(rights == "", amount == type(uint256).max)) { + uint256 rightsBalance = (_validateFrom(Hashes.allLocation(from, rights, to), from) || _validateFrom(Hashes.contractLocation(from, rights, to, contract_), from)) + ? type(uint256).max + : _loadDelegationUint(Hashes.erc20Location(from, rights, to, contract_), Storage.POSITIONS_AMOUNT); + amount = Ops.max(rightsBalance, amount); + } + } + assembly ("memory-safe") { + mstore(0, amount) // Only first 32 bytes of scratch space being accessed + return(0, 32) // Direct return, skips Solidity's redundant copying to save gas + } + } + + /// @inheritdoc IDelegateRegistry + function checkDelegateForERC1155(address to, address from, address contract_, uint256 tokenId, bytes32 rights) external view override returns (uint256 amount) { + if (!_invalidFrom(from)) { + amount = (_validateFrom(Hashes.allLocation(from, "", to), from) || _validateFrom(Hashes.contractLocation(from, "", to, contract_), from)) + ? type(uint256).max + : _loadDelegationUint(Hashes.erc1155Location(from, "", to, tokenId, contract_), Storage.POSITIONS_AMOUNT); + if (!Ops.or(rights == "", amount == type(uint256).max)) { + uint256 rightsBalance = (_validateFrom(Hashes.allLocation(from, rights, to), from) || _validateFrom(Hashes.contractLocation(from, rights, to, contract_), from)) + ? type(uint256).max + : _loadDelegationUint(Hashes.erc1155Location(from, rights, to, tokenId, contract_), Storage.POSITIONS_AMOUNT); + amount = Ops.max(rightsBalance, amount); + } + } + assembly ("memory-safe") { + mstore(0, amount) // Only first 32 bytes of scratch space is accessed + return(0, 32) // Direct return, skips Solidity's redundant copying to save gas + } + } + + /** + * ----------- ENUMERATIONS ----------- + */ + + /// @inheritdoc IDelegateRegistry + function getIncomingDelegations(address to) external view override returns (Delegation[] memory delegations_) { + delegations_ = _getValidDelegationsFromHashes(incomingDelegationHashes[to]); + } + + /// @inheritdoc IDelegateRegistry + function getOutgoingDelegations(address from) external view returns (Delegation[] memory delegations_) { + delegations_ = _getValidDelegationsFromHashes(outgoingDelegationHashes[from]); + } + + /// @inheritdoc IDelegateRegistry + function getIncomingDelegationHashes(address to) external view returns (bytes32[] memory delegationHashes) { + delegationHashes = _getValidDelegationHashesFromHashes(incomingDelegationHashes[to]); + } + + /// @inheritdoc IDelegateRegistry + function getOutgoingDelegationHashes(address from) external view returns (bytes32[] memory delegationHashes) { + delegationHashes = _getValidDelegationHashesFromHashes(outgoingDelegationHashes[from]); + } + + /// @inheritdoc IDelegateRegistry + function getDelegationsFromHashes(bytes32[] calldata hashes) external view returns (Delegation[] memory delegations_) { + delegations_ = new Delegation[](hashes.length); + unchecked { + for (uint256 i = 0; i < hashes.length; ++i) { + bytes32 location = Hashes.location(hashes[i]); + address from = _loadFrom(location); + if (_invalidFrom(from)) { + delegations_[i] = Delegation({type_: DelegationType.NONE, to: address(0), from: address(0), rights: "", amount: 0, contract_: address(0), tokenId: 0}); + } else { + (, address to, address contract_) = _loadDelegationAddresses(location); + delegations_[i] = Delegation({ + type_: Hashes.decodeType(hashes[i]), + to: to, + from: from, + rights: _loadDelegationBytes32(location, Storage.POSITIONS_RIGHTS), + amount: _loadDelegationUint(location, Storage.POSITIONS_AMOUNT), + contract_: contract_, + tokenId: _loadDelegationUint(location, Storage.POSITIONS_TOKEN_ID) + }); + } + } + } + } + + /** + * ----------- EXTERNAL STORAGE ACCESS ----------- + */ + + function readSlot(bytes32 location) external view returns (bytes32 contents) { + assembly { + contents := sload(location) + } + } + + function readSlots(bytes32[] calldata locations) external view returns (bytes32[] memory contents) { + uint256 length = locations.length; + contents = new bytes32[](length); + bytes32 tempLocation; + bytes32 tempValue; + unchecked { + for (uint256 i = 0; i < length; ++i) { + tempLocation = locations[i]; + assembly { + tempValue := sload(tempLocation) + } + contents[i] = tempValue; + } + } + } + + /** + * ----------- ERC165 ----------- + */ + + /// @notice Query if a contract implements an ERC-165 interface + /// @param interfaceId The interface identifier + /// @return valid Whether the queried interface is supported + function supportsInterface(bytes4 interfaceId) external pure returns (bool) { + return Ops.or(interfaceId == type(IDelegateRegistry).interfaceId, interfaceId == 0x01ffc9a7); + } + + /** + * ----------- INTERNAL ----------- + */ + + /// @dev Helper function to push new delegation hashes to the incoming and outgoing hashes mappings + function _pushDelegationHashes(address from, address to, bytes32 delegationHash) internal { + outgoingDelegationHashes[from].push(delegationHash); + incomingDelegationHashes[to].push(delegationHash); + } + + /// @dev Helper function that writes bytes32 data to delegation data location at array position + function _writeDelegation(bytes32 location, uint256 position, bytes32 data) internal { + assembly { + sstore(add(location, position), data) + } + } + + /// @dev Helper function that writes uint256 data to delegation data location at array position + function _writeDelegation(bytes32 location, uint256 position, uint256 data) internal { + assembly { + sstore(add(location, position), data) + } + } + + /// @dev Helper function that writes addresses according to the packing rule for delegation storage + function _writeDelegationAddresses(bytes32 location, address from, address to, address contract_) internal { + (bytes32 firstSlot, bytes32 secondSlot) = Storage.packAddresses(from, to, contract_); + uint256 firstPacked = Storage.POSITIONS_FIRST_PACKED; + uint256 secondPacked = Storage.POSITIONS_SECOND_PACKED; + assembly { + sstore(add(location, firstPacked), firstSlot) + sstore(add(location, secondPacked), secondSlot) + } + } + + /// @dev Helper function that writes `from` while preserving the rest of the storage slot + function _updateFrom(bytes32 location, address from) internal { + uint256 firstPacked = Storage.POSITIONS_FIRST_PACKED; + uint256 cleanAddress = Storage.CLEAN_ADDRESS; + uint256 cleanUpper12Bytes = type(uint256).max << 160; + assembly { + let slot := and(sload(add(location, firstPacked)), cleanUpper12Bytes) + sstore(add(location, firstPacked), or(slot, and(from, cleanAddress))) + } + } + + /// @dev Helper function that takes an array of delegation hashes and returns an array of Delegation structs with their onchain information + function _getValidDelegationsFromHashes(bytes32[] storage hashes) internal view returns (Delegation[] memory delegations_) { + uint256 count = 0; + uint256 hashesLength = hashes.length; + bytes32 hash; + bytes32[] memory filteredHashes = new bytes32[](hashesLength); + unchecked { + for (uint256 i = 0; i < hashesLength; ++i) { + hash = hashes[i]; + if (_invalidFrom(_loadFrom(Hashes.location(hash)))) continue; + filteredHashes[count++] = hash; + } + delegations_ = new Delegation[](count); + bytes32 location; + for (uint256 i = 0; i < count; ++i) { + hash = filteredHashes[i]; + location = Hashes.location(hash); + (address from, address to, address contract_) = _loadDelegationAddresses(location); + delegations_[i] = Delegation({ + type_: Hashes.decodeType(hash), + to: to, + from: from, + rights: _loadDelegationBytes32(location, Storage.POSITIONS_RIGHTS), + amount: _loadDelegationUint(location, Storage.POSITIONS_AMOUNT), + contract_: contract_, + tokenId: _loadDelegationUint(location, Storage.POSITIONS_TOKEN_ID) + }); + } + } + } + + /// @dev Helper function that takes an array of delegation hashes and returns an array of valid delegation hashes + function _getValidDelegationHashesFromHashes(bytes32[] storage hashes) internal view returns (bytes32[] memory validHashes) { + uint256 count = 0; + uint256 hashesLength = hashes.length; + bytes32 hash; + bytes32[] memory filteredHashes = new bytes32[](hashesLength); + unchecked { + for (uint256 i = 0; i < hashesLength; ++i) { + hash = hashes[i]; + if (_invalidFrom(_loadFrom(Hashes.location(hash)))) continue; + filteredHashes[count++] = hash; + } + validHashes = new bytes32[](count); + for (uint256 i = 0; i < count; ++i) { + validHashes[i] = filteredHashes[i]; + } + } + } + + /// @dev Helper function that loads delegation data from a particular array position and returns as bytes32 + function _loadDelegationBytes32(bytes32 location, uint256 position) internal view returns (bytes32 data) { + assembly { + data := sload(add(location, position)) + } + } + + /// @dev Helper function that loads delegation data from a particular array position and returns as uint256 + function _loadDelegationUint(bytes32 location, uint256 position) internal view returns (uint256 data) { + assembly { + data := sload(add(location, position)) + } + } + + // @dev Helper function that loads the from address from storage according to the packing rule for delegation storage + function _loadFrom(bytes32 location) internal view returns (address) { + bytes32 data; + uint256 firstPacked = Storage.POSITIONS_FIRST_PACKED; + assembly { + data := sload(add(location, firstPacked)) + } + return Storage.unpackAddress(data); + } + + /// @dev Helper function to establish whether a delegation is enabled + function _validateFrom(bytes32 location, address from) internal view returns (bool) { + return (from == _loadFrom(location)); + } + + /// @dev Helper function that loads the address for the delegation according to the packing rule for delegation storage + function _loadDelegationAddresses(bytes32 location) internal view returns (address from, address to, address contract_) { + bytes32 firstSlot; + bytes32 secondSlot; + uint256 firstPacked = Storage.POSITIONS_FIRST_PACKED; + uint256 secondPacked = Storage.POSITIONS_SECOND_PACKED; + assembly { + firstSlot := sload(add(location, firstPacked)) + secondSlot := sload(add(location, secondPacked)) + } + (from, to, contract_) = Storage.unpackAddresses(firstSlot, secondSlot); + } + + function _invalidFrom(address from) internal pure returns (bool) { + return Ops.or(from == Storage.DELEGATION_EMPTY, from == Storage.DELEGATION_REVOKED); + } +} diff --git a/contracts/dependencies/delegation/DelegationRegistry.sol b/contracts/dependencies/delegation/DelegationRegistry.sol deleted file mode 100644 index b0c0bf73c..000000000 --- a/contracts/dependencies/delegation/DelegationRegistry.sol +++ /dev/null @@ -1,441 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import {IDelegationRegistry} from "./IDelegationRegistry.sol"; -import {EnumerableSet} from "../openzeppelin/contracts/EnumerableSet.sol"; -import {ERC165} from "../openzeppelin/contracts/ERC165.sol"; - -/** - * @title DelegationRegistry - * @custom:version 1.0 - * @notice An immutable registry contract to be deployed as a standalone primitive. - * @dev See EIP-5639, new project launches can read previous cold wallet -> hot wallet delegations - * from here and integrate those permissions into their flow. - * @custom:coauthor foobar (0xfoobar) - * @custom:coauthor wwchung (manifoldxyz) - * @custom:coauthor purplehat (artblocks) - * @custom:coauthor ryley-o (artblocks) - * @custom:coauthor andy8052 (tessera) - * @custom:coauthor punk6529 (open metaverse) - * @custom:coauthor loopify (loopiverse) - * @custom:coauthor emiliano (nftrentals) - * @custom:coauthor arran (proof) - * @custom:coauthor james (collabland) - * @custom:coauthor john (gnosis safe) - * @custom:coauthor 0xrusowsky - */ -contract DelegationRegistry is IDelegationRegistry, ERC165 { - using EnumerableSet for EnumerableSet.AddressSet; - using EnumerableSet for EnumerableSet.Bytes32Set; - - /// @notice The global mapping and single source of truth for delegations - /// @dev vault -> vaultVersion -> delegationHash - mapping(address => mapping(uint256 => EnumerableSet.Bytes32Set)) internal delegations; - - /// @notice A mapping of wallets to versions (for cheap revocation) - mapping(address => uint256) internal vaultVersion; - - /// @notice A mapping of wallets to delegates to versions (for cheap revocation) - mapping(address => mapping(address => uint256)) internal delegateVersion; - - /// @notice A secondary mapping to return onchain enumerability of delegations that a given address can perform - /// @dev delegate -> delegationHashes - mapping(address => EnumerableSet.Bytes32Set) internal delegationHashes; - - /// @notice A secondary mapping used to return delegation information about a delegation - /// @dev delegationHash -> DelegateInfo - mapping(bytes32 => IDelegationRegistry.DelegationInfo) internal delegationInfo; - - /** - * @inheritdoc ERC165 - */ - function supportsInterface(bytes4 interfaceId) public view virtual override (ERC165) returns (bool) { - return interfaceId == type(IDelegationRegistry).interfaceId || super.supportsInterface(interfaceId); - } - - /** - * ----------- WRITE ----------- - */ - - /** - * @inheritdoc IDelegationRegistry - */ - function delegateForAll(address delegate, bool value) external override { - bytes32 delegationHash = _computeAllDelegationHash(msg.sender, delegate); - _setDelegationValues( - delegate, delegationHash, value, IDelegationRegistry.DelegationType.ALL, msg.sender, address(0), 0 - ); - emit IDelegationRegistry.DelegateForAll(msg.sender, delegate, value); - } - - /** - * @inheritdoc IDelegationRegistry - */ - function delegateForContract(address delegate, address contract_, bool value) external override { - bytes32 delegationHash = _computeContractDelegationHash(msg.sender, delegate, contract_); - _setDelegationValues( - delegate, delegationHash, value, IDelegationRegistry.DelegationType.CONTRACT, msg.sender, contract_, 0 - ); - emit IDelegationRegistry.DelegateForContract(msg.sender, delegate, contract_, value); - } - - /** - * @inheritdoc IDelegationRegistry - */ - function delegateForToken(address delegate, address contract_, uint256 tokenId, bool value) external override { - bytes32 delegationHash = _computeTokenDelegationHash(msg.sender, delegate, contract_, tokenId); - _setDelegationValues( - delegate, delegationHash, value, IDelegationRegistry.DelegationType.TOKEN, msg.sender, contract_, tokenId - ); - emit IDelegationRegistry.DelegateForToken(msg.sender, delegate, contract_, tokenId, value); - } - - /** - * @dev Helper function to set all delegation values and enumeration sets - */ - function _setDelegationValues( - address delegate, - bytes32 delegateHash, - bool value, - IDelegationRegistry.DelegationType type_, - address vault, - address contract_, - uint256 tokenId - ) internal { - if (value) { - delegations[vault][vaultVersion[vault]].add(delegateHash); - delegationHashes[delegate].add(delegateHash); - delegationInfo[delegateHash] = - DelegationInfo({vault: vault, delegate: delegate, type_: type_, contract_: contract_, tokenId: tokenId}); - } else { - delegations[vault][vaultVersion[vault]].remove(delegateHash); - delegationHashes[delegate].remove(delegateHash); - delete delegationInfo[delegateHash]; - } - } - - /** - * @dev Helper function to compute delegation hash for wallet delegation - */ - function _computeAllDelegationHash(address vault, address delegate) internal view returns (bytes32) { - uint256 vaultVersion_ = vaultVersion[vault]; - uint256 delegateVersion_ = delegateVersion[vault][delegate]; - return keccak256(abi.encode(delegate, vault, vaultVersion_, delegateVersion_)); - } - - /** - * @dev Helper function to compute delegation hash for contract delegation - */ - function _computeContractDelegationHash(address vault, address delegate, address contract_) - internal - view - returns (bytes32) - { - uint256 vaultVersion_ = vaultVersion[vault]; - uint256 delegateVersion_ = delegateVersion[vault][delegate]; - return keccak256(abi.encode(delegate, vault, contract_, vaultVersion_, delegateVersion_)); - } - - /** - * @dev Helper function to compute delegation hash for token delegation - */ - function _computeTokenDelegationHash(address vault, address delegate, address contract_, uint256 tokenId) - internal - view - returns (bytes32) - { - uint256 vaultVersion_ = vaultVersion[vault]; - uint256 delegateVersion_ = delegateVersion[vault][delegate]; - return keccak256(abi.encode(delegate, vault, contract_, tokenId, vaultVersion_, delegateVersion_)); - } - - /** - * @inheritdoc IDelegationRegistry - */ - function revokeAllDelegates() external override { - ++vaultVersion[msg.sender]; - emit IDelegationRegistry.RevokeAllDelegates(msg.sender); - } - - /** - * @inheritdoc IDelegationRegistry - */ - function revokeDelegate(address delegate) external override { - _revokeDelegate(delegate, msg.sender); - } - - /** - * @inheritdoc IDelegationRegistry - */ - function revokeSelf(address vault) external override { - _revokeDelegate(msg.sender, vault); - } - - /** - * @dev Revoke the `delegate` hotwallet from the `vault` coldwallet. - */ - function _revokeDelegate(address delegate, address vault) internal { - ++delegateVersion[vault][delegate]; - // For enumerations, filter in the view functions - emit IDelegationRegistry.RevokeDelegate(vault, msg.sender); - } - - /** - * ----------- READ ----------- - */ - - /** - * @inheritdoc IDelegationRegistry - */ - function getDelegationsByDelegate(address delegate) - external - view - returns (IDelegationRegistry.DelegationInfo[] memory info) - { - EnumerableSet.Bytes32Set storage potentialDelegationHashes = delegationHashes[delegate]; - uint256 potentialDelegationHashesLength = potentialDelegationHashes.length(); - uint256 delegationCount = 0; - info = new IDelegationRegistry.DelegationInfo[](potentialDelegationHashesLength); - for (uint256 i = 0; i < potentialDelegationHashesLength;) { - bytes32 delegateHash = potentialDelegationHashes.at(i); - IDelegationRegistry.DelegationInfo memory delegationInfo_ = delegationInfo[delegateHash]; - address vault = delegationInfo_.vault; - IDelegationRegistry.DelegationType type_ = delegationInfo_.type_; - bool valid = false; - if (type_ == IDelegationRegistry.DelegationType.ALL) { - if (delegateHash == _computeAllDelegationHash(vault, delegate)) { - valid = true; - } - } else if (type_ == IDelegationRegistry.DelegationType.CONTRACT) { - if (delegateHash == _computeContractDelegationHash(vault, delegate, delegationInfo_.contract_)) { - valid = true; - } - } else if (type_ == IDelegationRegistry.DelegationType.TOKEN) { - if ( - delegateHash - == _computeTokenDelegationHash(vault, delegate, delegationInfo_.contract_, delegationInfo_.tokenId) - ) { - valid = true; - } - } - if (valid) { - info[delegationCount++] = delegationInfo_; - } - unchecked { - ++i; - } - } - if (potentialDelegationHashesLength > delegationCount) { - assembly { - let decrease := sub(potentialDelegationHashesLength, delegationCount) - mstore(info, sub(mload(info), decrease)) - } - } - } - - /** - * @inheritdoc IDelegationRegistry - */ - function getDelegatesForAll(address vault) external view returns (address[] memory delegates) { - return _getDelegatesForLevel(vault, IDelegationRegistry.DelegationType.ALL, address(0), 0); - } - - /** - * @inheritdoc IDelegationRegistry - */ - function getDelegatesForContract(address vault, address contract_) - external - view - override - returns (address[] memory delegates) - { - return _getDelegatesForLevel(vault, IDelegationRegistry.DelegationType.CONTRACT, contract_, 0); - } - - /** - * @inheritdoc IDelegationRegistry - */ - function getDelegatesForToken(address vault, address contract_, uint256 tokenId) - external - view - override - returns (address[] memory delegates) - { - return _getDelegatesForLevel(vault, IDelegationRegistry.DelegationType.TOKEN, contract_, tokenId); - } - - function _getDelegatesForLevel( - address vault, - IDelegationRegistry.DelegationType delegationType, - address contract_, - uint256 tokenId - ) internal view returns (address[] memory delegates) { - EnumerableSet.Bytes32Set storage delegationHashes_ = delegations[vault][vaultVersion[vault]]; - uint256 potentialDelegatesLength = delegationHashes_.length(); - uint256 delegatesCount = 0; - delegates = new address[](potentialDelegatesLength); - for (uint256 i = 0; i < potentialDelegatesLength;) { - bytes32 delegationHash = delegationHashes_.at(i); - DelegationInfo storage delegationInfo_ = delegationInfo[delegationHash]; - if (delegationInfo_.type_ == delegationType) { - if (delegationType == IDelegationRegistry.DelegationType.ALL) { - // check delegate version by validating the hash - if (delegationHash == _computeAllDelegationHash(vault, delegationInfo_.delegate)) { - delegates[delegatesCount++] = delegationInfo_.delegate; - } - } else if (delegationType == IDelegationRegistry.DelegationType.CONTRACT) { - if (delegationInfo_.contract_ == contract_) { - // check delegate version by validating the hash - if ( - delegationHash == _computeContractDelegationHash(vault, delegationInfo_.delegate, contract_) - ) { - delegates[delegatesCount++] = delegationInfo_.delegate; - } - } - } else if (delegationType == IDelegationRegistry.DelegationType.TOKEN) { - if (delegationInfo_.contract_ == contract_ && delegationInfo_.tokenId == tokenId) { - // check delegate version by validating the hash - if ( - delegationHash - == _computeTokenDelegationHash(vault, delegationInfo_.delegate, contract_, tokenId) - ) { - delegates[delegatesCount++] = delegationInfo_.delegate; - } - } - } - } - unchecked { - ++i; - } - } - if (potentialDelegatesLength > delegatesCount) { - assembly { - let decrease := sub(potentialDelegatesLength, delegatesCount) - mstore(delegates, sub(mload(delegates), decrease)) - } - } - } - - /** - * @inheritdoc IDelegationRegistry - */ - function getContractLevelDelegations(address vault) - external - view - returns (IDelegationRegistry.ContractDelegation[] memory contractDelegations) - { - EnumerableSet.Bytes32Set storage delegationHashes_ = delegations[vault][vaultVersion[vault]]; - uint256 potentialLength = delegationHashes_.length(); - uint256 delegationCount = 0; - contractDelegations = new IDelegationRegistry.ContractDelegation[](potentialLength); - for (uint256 i = 0; i < potentialLength;) { - bytes32 delegationHash = delegationHashes_.at(i); - DelegationInfo storage delegationInfo_ = delegationInfo[delegationHash]; - if (delegationInfo_.type_ == IDelegationRegistry.DelegationType.CONTRACT) { - // check delegate version by validating the hash - if ( - delegationHash - == _computeContractDelegationHash(vault, delegationInfo_.delegate, delegationInfo_.contract_) - ) { - contractDelegations[delegationCount++] = IDelegationRegistry.ContractDelegation({ - contract_: delegationInfo_.contract_, - delegate: delegationInfo_.delegate - }); - } - } - unchecked { - ++i; - } - } - if (potentialLength > delegationCount) { - assembly { - let decrease := sub(potentialLength, delegationCount) - mstore(contractDelegations, sub(mload(contractDelegations), decrease)) - } - } - } - - /** - * @inheritdoc IDelegationRegistry - */ - function getTokenLevelDelegations(address vault) - external - view - returns (IDelegationRegistry.TokenDelegation[] memory tokenDelegations) - { - EnumerableSet.Bytes32Set storage delegationHashes_ = delegations[vault][vaultVersion[vault]]; - uint256 potentialLength = delegationHashes_.length(); - uint256 delegationCount = 0; - tokenDelegations = new IDelegationRegistry.TokenDelegation[](potentialLength); - for (uint256 i = 0; i < potentialLength;) { - bytes32 delegationHash = delegationHashes_.at(i); - DelegationInfo storage delegationInfo_ = delegationInfo[delegationHash]; - if (delegationInfo_.type_ == IDelegationRegistry.DelegationType.TOKEN) { - // check delegate version by validating the hash - if ( - delegationHash - == _computeTokenDelegationHash( - vault, delegationInfo_.delegate, delegationInfo_.contract_, delegationInfo_.tokenId - ) - ) { - tokenDelegations[delegationCount++] = IDelegationRegistry.TokenDelegation({ - contract_: delegationInfo_.contract_, - tokenId: delegationInfo_.tokenId, - delegate: delegationInfo_.delegate - }); - } - } - unchecked { - ++i; - } - } - if (potentialLength > delegationCount) { - assembly { - let decrease := sub(potentialLength, delegationCount) - mstore(tokenDelegations, sub(mload(tokenDelegations), decrease)) - } - } - } - - /** - * @inheritdoc IDelegationRegistry - */ - function checkDelegateForAll(address delegate, address vault) public view override returns (bool) { - bytes32 delegateHash = - keccak256(abi.encode(delegate, vault, vaultVersion[vault], delegateVersion[vault][delegate])); - return delegations[vault][vaultVersion[vault]].contains(delegateHash); - } - - /** - * @inheritdoc IDelegationRegistry - */ - function checkDelegateForContract(address delegate, address vault, address contract_) - public - view - override - returns (bool) - { - bytes32 delegateHash = - keccak256(abi.encode(delegate, vault, contract_, vaultVersion[vault], delegateVersion[vault][delegate])); - return delegations[vault][vaultVersion[vault]].contains(delegateHash) - ? true - : checkDelegateForAll(delegate, vault); - } - - /** - * @inheritdoc IDelegationRegistry - */ - function checkDelegateForToken(address delegate, address vault, address contract_, uint256 tokenId) - public - view - override - returns (bool) - { - bytes32 delegateHash = keccak256( - abi.encode(delegate, vault, contract_, tokenId, vaultVersion[vault], delegateVersion[vault][delegate]) - ); - return delegations[vault][vaultVersion[vault]].contains(delegateHash) - ? true - : checkDelegateForContract(delegate, vault, contract_); - } -} \ No newline at end of file diff --git a/contracts/dependencies/delegation/IDelegateRegistry.sol b/contracts/dependencies/delegation/IDelegateRegistry.sol new file mode 100644 index 000000000..8c7cb1015 --- /dev/null +++ b/contracts/dependencies/delegation/IDelegateRegistry.sol @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: CC0-1.0 +pragma solidity >=0.8.13; + +/** + * @title IDelegateRegistry + * @custom:version 2.0 + * @custom:author foobar (0xfoobar) + * @notice A standalone immutable registry storing delegated permissions from one address to another + */ +interface IDelegateRegistry { + /// @notice Delegation type, NONE is used when a delegation does not exist or is revoked + enum DelegationType { + NONE, + ALL, + CONTRACT, + ERC721, + ERC20, + ERC1155 + } + + /// @notice Struct for returning delegations + struct Delegation { + DelegationType type_; + address to; + address from; + bytes32 rights; + address contract_; + uint256 tokenId; + uint256 amount; + } + + /// @notice Emitted when an address delegates or revokes rights for their entire wallet + event DelegateAll(address indexed from, address indexed to, bytes32 rights, bool enable); + + /// @notice Emitted when an address delegates or revokes rights for a contract address + event DelegateContract(address indexed from, address indexed to, address indexed contract_, bytes32 rights, bool enable); + + /// @notice Emitted when an address delegates or revokes rights for an ERC721 tokenId + event DelegateERC721(address indexed from, address indexed to, address indexed contract_, uint256 tokenId, bytes32 rights, bool enable); + + /// @notice Emitted when an address delegates or revokes rights for an amount of ERC20 tokens + event DelegateERC20(address indexed from, address indexed to, address indexed contract_, bytes32 rights, uint256 amount); + + /// @notice Emitted when an address delegates or revokes rights for an amount of an ERC1155 tokenId + event DelegateERC1155(address indexed from, address indexed to, address indexed contract_, uint256 tokenId, bytes32 rights, uint256 amount); + + /// @notice Thrown if multicall calldata is malformed + error MulticallFailed(); + + /** + * ----------- WRITE ----------- + */ + + /** + * @notice Call multiple functions in the current contract and return the data from all of them if they all succeed + * @param data The encoded function data for each of the calls to make to this contract + * @return results The results from each of the calls passed in via data + */ + function multicall(bytes[] calldata data) external payable returns (bytes[] memory results); + + /** + * @notice Allow the delegate to act on behalf of `msg.sender` for all contracts + * @param to The address to act as delegate + * @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights + * @param enable Whether to enable or disable this delegation, true delegates and false revokes + * @return delegationHash The unique identifier of the delegation + */ + function delegateAll(address to, bytes32 rights, bool enable) external payable returns (bytes32 delegationHash); + + /** + * @notice Allow the delegate to act on behalf of `msg.sender` for a specific contract + * @param to The address to act as delegate + * @param contract_ The contract whose rights are being delegated + * @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights + * @param enable Whether to enable or disable this delegation, true delegates and false revokes + * @return delegationHash The unique identifier of the delegation + */ + function delegateContract(address to, address contract_, bytes32 rights, bool enable) external payable returns (bytes32 delegationHash); + + /** + * @notice Allow the delegate to act on behalf of `msg.sender` for a specific ERC721 token + * @param to The address to act as delegate + * @param contract_ The contract whose rights are being delegated + * @param tokenId The token id to delegate + * @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights + * @param enable Whether to enable or disable this delegation, true delegates and false revokes + * @return delegationHash The unique identifier of the delegation + */ + function delegateERC721(address to, address contract_, uint256 tokenId, bytes32 rights, bool enable) external payable returns (bytes32 delegationHash); + + /** + * @notice Allow the delegate to act on behalf of `msg.sender` for a specific amount of ERC20 tokens + * @dev The actual amount is not encoded in the hash, just the existence of a amount (since it is an upper bound) + * @param to The address to act as delegate + * @param contract_ The address for the fungible token contract + * @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights + * @param amount The amount to delegate, > 0 delegates and 0 revokes + * @return delegationHash The unique identifier of the delegation + */ + function delegateERC20(address to, address contract_, bytes32 rights, uint256 amount) external payable returns (bytes32 delegationHash); + + /** + * @notice Allow the delegate to act on behalf of `msg.sender` for a specific amount of ERC1155 tokens + * @dev The actual amount is not encoded in the hash, just the existence of a amount (since it is an upper bound) + * @param to The address to act as delegate + * @param contract_ The address of the contract that holds the token + * @param tokenId The token id to delegate + * @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights + * @param amount The amount of that token id to delegate, > 0 delegates and 0 revokes + * @return delegationHash The unique identifier of the delegation + */ + function delegateERC1155(address to, address contract_, uint256 tokenId, bytes32 rights, uint256 amount) external payable returns (bytes32 delegationHash); + + /** + * ----------- CHECKS ----------- + */ + + /** + * @notice Check if `to` is a delegate of `from` for the entire wallet + * @param to The potential delegate address + * @param from The potential address who delegated rights + * @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only + * @return valid Whether delegate is granted to act on the from's behalf + */ + function checkDelegateForAll(address to, address from, bytes32 rights) external view returns (bool); + + /** + * @notice Check if `to` is a delegate of `from` for the specified `contract_` or the entire wallet + * @param to The delegated address to check + * @param contract_ The specific contract address being checked + * @param from The cold wallet who issued the delegation + * @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only + * @return valid Whether delegate is granted to act on from's behalf for entire wallet or that specific contract + */ + function checkDelegateForContract(address to, address from, address contract_, bytes32 rights) external view returns (bool); + + /** + * @notice Check if `to` is a delegate of `from` for the specific `contract` and `tokenId`, the entire `contract_`, or the entire wallet + * @param to The delegated address to check + * @param contract_ The specific contract address being checked + * @param tokenId The token id for the token to delegating + * @param from The wallet that issued the delegation + * @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only + * @return valid Whether delegate is granted to act on from's behalf for entire wallet, that contract, or that specific tokenId + */ + function checkDelegateForERC721(address to, address from, address contract_, uint256 tokenId, bytes32 rights) external view returns (bool); + + /** + * @notice Returns the amount of ERC20 tokens the delegate is granted rights to act on the behalf of + * @param to The delegated address to check + * @param contract_ The address of the token contract + * @param from The cold wallet who issued the delegation + * @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only + * @return balance The delegated balance, which will be 0 if the delegation does not exist + */ + function checkDelegateForERC20(address to, address from, address contract_, bytes32 rights) external view returns (uint256); + + /** + * @notice Returns the amount of a ERC1155 tokens the delegate is granted rights to act on the behalf of + * @param to The delegated address to check + * @param contract_ The address of the token contract + * @param tokenId The token id to check the delegated amount of + * @param from The cold wallet who issued the delegation + * @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only + * @return balance The delegated balance, which will be 0 if the delegation does not exist + */ + function checkDelegateForERC1155(address to, address from, address contract_, uint256 tokenId, bytes32 rights) external view returns (uint256); + + /** + * ----------- ENUMERATIONS ----------- + */ + + /** + * @notice Returns all enabled delegations a given delegate has received + * @param to The address to retrieve delegations for + * @return delegations Array of Delegation structs + */ + function getIncomingDelegations(address to) external view returns (Delegation[] memory delegations); + + /** + * @notice Returns all enabled delegations an address has given out + * @param from The address to retrieve delegations for + * @return delegations Array of Delegation structs + */ + function getOutgoingDelegations(address from) external view returns (Delegation[] memory delegations); + + /** + * @notice Returns all hashes associated with enabled delegations an address has received + * @param to The address to retrieve incoming delegation hashes for + * @return delegationHashes Array of delegation hashes + */ + function getIncomingDelegationHashes(address to) external view returns (bytes32[] memory delegationHashes); + + /** + * @notice Returns all hashes associated with enabled delegations an address has given out + * @param from The address to retrieve outgoing delegation hashes for + * @return delegationHashes Array of delegation hashes + */ + function getOutgoingDelegationHashes(address from) external view returns (bytes32[] memory delegationHashes); + + /** + * @notice Returns the delegations for a given array of delegation hashes + * @param delegationHashes is an array of hashes that correspond to delegations + * @return delegations Array of Delegation structs, return empty structs for nonexistent or revoked delegations + */ + function getDelegationsFromHashes(bytes32[] calldata delegationHashes) external view returns (Delegation[] memory delegations); + + /** + * ----------- STORAGE ACCESS ----------- + */ + + /** + * @notice Allows external contracts to read arbitrary storage slots + */ + function readSlot(bytes32 location) external view returns (bytes32); + + /** + * @notice Allows external contracts to read an arbitrary array of storage slots + */ + function readSlots(bytes32[] calldata locations) external view returns (bytes32[] memory); +} diff --git a/contracts/dependencies/delegation/libraries/RegistryHashes.sol b/contracts/dependencies/delegation/libraries/RegistryHashes.sol new file mode 100644 index 000000000..6437df11c --- /dev/null +++ b/contracts/dependencies/delegation/libraries/RegistryHashes.sol @@ -0,0 +1,303 @@ +// SPDX-License-Identifier: CC0-1.0 +pragma solidity ^0.8.21; + +import {IDelegateRegistry} from "../IDelegateRegistry.sol"; + +/** + * @title Library for calculating the hashes and storage locations used in the delegate registry + * + * The encoding for the 5 types of delegate registry hashes should be as follows: + * + * ALL: keccak256(abi.encodePacked(rights, from, to)) + * CONTRACT: keccak256(abi.encodePacked(rights, from, to, contract_)) + * ERC721: keccak256(abi.encodePacked(rights, from, to, contract_, tokenId)) + * ERC20: keccak256(abi.encodePacked(rights, from, to, contract_)) + * ERC1155: keccak256(abi.encodePacked(rights, from, to, contract_, tokenId)) + * + * To avoid collisions between the hashes with respect to type, the hash is shifted left by one byte + * and the last byte is then encoded with a unique number for the delegation type + * + */ +library RegistryHashes { + /// @dev Used to delete everything but the last byte of a 32 byte word with and(word, EXTRACT_LAST_BYTE) + uint256 internal constant EXTRACT_LAST_BYTE = 0xff; + /// @dev Constants for the delegate registry delegation type enumeration + uint256 internal constant ALL_TYPE = 1; + uint256 internal constant CONTRACT_TYPE = 2; + uint256 internal constant ERC721_TYPE = 3; + uint256 internal constant ERC20_TYPE = 4; + uint256 internal constant ERC1155_TYPE = 5; + /// @dev Constant for the location of the delegations array in the delegate registry, defined to be zero + uint256 internal constant DELEGATION_SLOT = 0; + + /** + * @notice Helper function to decode last byte of a delegation hash into its delegation type enum + * @param inputHash The bytehash to decode the type from + * @return decodedType The delegation type + */ + function decodeType(bytes32 inputHash) internal pure returns (IDelegateRegistry.DelegationType decodedType) { + assembly { + decodedType := and(inputHash, EXTRACT_LAST_BYTE) + } + } + + /** + * @notice Helper function that computes the storage location of a particular delegation array + * @dev Storage keys further down the array can be obtained by adding computedLocation with the element position + * @dev Follows the solidity storage location encoding for a mapping(bytes32 => fixedArray) at the position of the delegationSlot + * @param inputHash The bytehash to decode the type from + * @return computedLocation is the storage key of the delegation array at position 0 + */ + function location(bytes32 inputHash) internal pure returns (bytes32 computedLocation) { + assembly ("memory-safe") { + // This block only allocates memory in the scratch space + mstore(0, inputHash) + mstore(32, DELEGATION_SLOT) + computedLocation := keccak256(0, 64) // Run keccak256 over bytes in scratch space to obtain the storage key + } + } + + /** + * @notice Helper function to compute delegation hash for `DelegationType.ALL` + * @dev Equivalent to `keccak256(abi.encodePacked(rights, from, to))` then left-shift by 1 byte and write the delegation type to the cleaned last byte + * @dev Will not revert if `from` or `to` are > uint160, any input larger than uint160 for `from` and `to` will be cleaned to its lower 20 bytes + * @param from The address making the delegation + * @param rights The rights specified by the delegation + * @param to The address receiving the delegation + * @return hash The delegation parameters encoded with ALL_TYPE + */ + function allHash(address from, bytes32 rights, address to) internal pure returns (bytes32 hash) { + assembly ("memory-safe") { + // This block only allocates memory after the free memory pointer + let ptr := mload(64) // Load the free memory pointer + // Lay out the variables from last to first, agnostic to upper 96 bits of address words. + mstore(add(ptr, 40), to) + mstore(add(ptr, 20), from) + mstore(ptr, rights) + hash := or(shl(8, keccak256(ptr, 72)), ALL_TYPE) // Keccak-hashes the packed encoding, left-shifts by one byte, then writes type to the lowest-order byte + } + } + + /** + * @notice Helper function to compute delegation location for `DelegationType.ALL` + * @dev Equivalent to `location(allHash(rights, from, to))` + * @dev Will not revert if `from` or `to` are > uint160, any input larger than uint160 for `from` and `to` will be cleaned to its lower 20 bytes + * @param from The address making the delegation + * @param rights The rights specified by the delegation + * @param to The address receiving the delegation + * @return computedLocation The storage location of the all delegation with those parameters in the delegations mapping + */ + function allLocation(address from, bytes32 rights, address to) internal pure returns (bytes32 computedLocation) { + assembly ("memory-safe") { + // This block only allocates memory after the free memory pointer and in the scratch space + let ptr := mload(64) // Load the free memory pointer + // Lay out the variables from last to first, agnostic to upper 96 bits of address words. + mstore(add(ptr, 40), to) + mstore(add(ptr, 20), from) + mstore(ptr, rights) + mstore(0, or(shl(8, keccak256(ptr, 72)), ALL_TYPE)) // Computes `allHash`, then stores the result in scratch space + mstore(32, DELEGATION_SLOT) + computedLocation := keccak256(0, 64) // Runs keccak over the scratch space to obtain the storage key + } + } + + /** + * @notice Helper function to compute delegation hash for `DelegationType.CONTRACT` + * @dev Equivalent to keccak256(abi.encodePacked(rights, from, to, contract_)) left-shifted by 1 then last byte overwritten with CONTRACT_TYPE + * @dev Will not revert if `from`, `to` or `contract_` are > uint160, these inputs will be cleaned to their lower 20 bytes + * @param from The address making the delegation + * @param rights The rights specified by the delegation + * @param to The address receiving the delegation + * @param contract_ The address of the contract specified by the delegation + * @return hash The delegation parameters encoded with CONTRACT_TYPE + */ + function contractHash(address from, bytes32 rights, address to, address contract_) internal pure returns (bytes32 hash) { + assembly ("memory-safe") { + // This block only allocates memory after the free memory pointer + let ptr := mload(64) // Load the free memory pointer + // Lay out the variables from last to first, agnostic to upper 96 bits of address words. + mstore(add(ptr, 60), contract_) + mstore(add(ptr, 40), to) + mstore(add(ptr, 20), from) + mstore(ptr, rights) + hash := or(shl(8, keccak256(ptr, 92)), CONTRACT_TYPE) // Keccak-hashes the packed encoding, left-shifts by one byte, then writes type to the lowest-order byte + } + } + + /** + * @notice Helper function to compute delegation location for `DelegationType.CONTRACT` + * @dev Equivalent to `location(contractHash(rights, from, to, contract_))` + * @dev Will not revert if `from`, `to` or `contract_` are > uint160, these inputs will be cleaned to their lower 20 bytes + * @param from The address making the delegation + * @param rights The rights specified by the delegation + * @param to The address receiving the delegation + * @param contract_ The address of the contract specified by the delegation + * @return computedLocation The storage location of the contract delegation with those parameters in the delegations mapping + */ + function contractLocation(address from, bytes32 rights, address to, address contract_) internal pure returns (bytes32 computedLocation) { + assembly ("memory-safe") { + // This block only allocates memory after the free memory pointer and in the scratch space + let ptr := mload(64) // Load free memory pointer + // Lay out the variables from last to first, agnostic to upper 96 bits of address words. + mstore(add(ptr, 60), contract_) + mstore(add(ptr, 40), to) + mstore(add(ptr, 20), from) + mstore(ptr, rights) + mstore(0, or(shl(8, keccak256(ptr, 92)), CONTRACT_TYPE)) // Computes `contractHash`, then stores the result in scratch space + mstore(32, DELEGATION_SLOT) + computedLocation := keccak256(0, 64) // Runs keccak over the scratch space to obtain the storage key + } + } + + /** + * @notice Helper function to compute delegation hash for `DelegationType.ERC721` + * @dev Equivalent to `keccak256(abi.encodePacked(rights, from, to, contract_, tokenId)) left-shifted by 1 then last byte overwritten with ERC721_TYPE + * @dev Will not revert if `from`, `to` or `contract_` are > uint160, these inputs will be cleaned to their lower 20 bytes + * @param from The address making the delegation + * @param rights The rights specified by the delegation + * @param to The address receiving the delegation + * @param tokenId The id of the token specified by the delegation + * @param contract_ The address of the contract specified by the delegation + * @return hash The delegation parameters encoded with ERC721_TYPE + */ + function erc721Hash(address from, bytes32 rights, address to, uint256 tokenId, address contract_) internal pure returns (bytes32 hash) { + assembly ("memory-safe") { + // This block only allocates memory after the free memory pointer + let ptr := mload(64) // Cache the free memory pointer. + // Lay out the variables from last to first, agnostic to upper 96 bits of address words. + mstore(add(ptr, 92), tokenId) + mstore(add(ptr, 60), contract_) + mstore(add(ptr, 40), to) + mstore(add(ptr, 20), from) + mstore(ptr, rights) + hash := or(shl(8, keccak256(ptr, 124)), ERC721_TYPE) // Keccak-hashes the packed encoding, left-shifts by one byte, then writes type to the lowest-order byte + } + } + + /** + * @notice Helper function to compute delegation location for `DelegationType.ERC721` + * @dev Equivalent to `location(ERC721Hash(rights, from, to, contract_, tokenId))` + * @dev Will not revert if `from`, `to` or `contract_` are > uint160, these inputs will be cleaned to their lower 20 bytes + * @param from The address making the delegation + * @param rights The rights specified by the delegation + * @param to The address receiving the delegation + * @param tokenId The id of the ERC721 token + * @param contract_ The address of the ERC721 token contract + * @return computedLocation The storage location of the ERC721 delegation with those parameters in the delegations mapping + */ + function erc721Location(address from, bytes32 rights, address to, uint256 tokenId, address contract_) internal pure returns (bytes32 computedLocation) { + assembly ("memory-safe") { + // This block only allocates memory after the free memory pointer and in the scratch space + let ptr := mload(64) // Cache the free memory pointer. + // Lay out the variables from last to first, agnostic to upper 96 bits of address words. + mstore(add(ptr, 92), tokenId) + mstore(add(ptr, 60), contract_) + mstore(add(ptr, 40), to) + mstore(add(ptr, 20), from) + mstore(ptr, rights) + mstore(0, or(shl(8, keccak256(ptr, 124)), ERC721_TYPE)) // Computes erc721Hash, then stores the result in scratch space + mstore(32, DELEGATION_SLOT) + computedLocation := keccak256(0, 64) // Runs keccak256 over the scratch space to obtain the storage key + } + } + + /** + * @notice Helper function to compute delegation hash for `DelegationType.ERC20` + * @dev Equivalent to `keccak256(abi.encodePacked(rights, from, to, contract_))` with the last byte overwritten with ERC20_TYPE + * @dev Will not revert if `from`, `to` or `contract_` are > uint160, these inputs will be cleaned to their lower 20 bytes + * @param from The address making the delegation + * @param rights The rights specified by the delegation + * @param to The address receiving the delegation + * @param contract_ The address of the ERC20 token contract + * @return hash The parameters encoded with ERC20_TYPE + */ + function erc20Hash(address from, bytes32 rights, address to, address contract_) internal pure returns (bytes32 hash) { + assembly ("memory-safe") { + // This block only allocates memory after the free memory pointer + let ptr := mload(64) // Load free memory pointer + // Lay out the variables from last to first, agnostic to upper 96 bits of address words. + mstore(add(ptr, 60), contract_) + mstore(add(ptr, 40), to) + mstore(add(ptr, 20), from) + mstore(ptr, rights) + hash := or(shl(8, keccak256(ptr, 92)), ERC20_TYPE) // Keccak-hashes the packed encoding, left-shifts by one byte, then writes type to the lowest-order byte + } + } + + /** + * @notice Helper function to compute delegation location for `DelegationType.ERC20` + * @dev Equivalent to `location(ERC20Hash(rights, from, to, contract_))` + * @dev Will not revert if `from`, `to` or `contract_` are > uint160, these inputs will be cleaned to their lower 20 bytes + * @param from The address making the delegation + * @param rights The rights specified by the delegation + * @param to The address receiving the delegation + * @param contract_ The address of the ERC20 token contract + * @return computedLocation The storage location of the ERC20 delegation with those parameters in the delegations mapping + */ + function erc20Location(address from, bytes32 rights, address to, address contract_) internal pure returns (bytes32 computedLocation) { + assembly ("memory-safe") { + // This block only allocates memory after the free memory pointer and in the scratch space + let ptr := mload(64) // Loads the free memory pointer + // Lay out the variables from last to first, agnostic to upper 96 bits of address words. + mstore(add(ptr, 60), contract_) + mstore(add(ptr, 40), to) + mstore(add(ptr, 20), from) + mstore(ptr, rights) + mstore(0, or(shl(8, keccak256(ptr, 92)), ERC20_TYPE)) // Computes erc20Hash, then stores the result in scratch space + mstore(32, DELEGATION_SLOT) + computedLocation := keccak256(0, 64) // Runs keccak over the scratch space to obtain the storage key + } + } + + /** + * @notice Helper function to compute delegation hash for `DelegationType.ERC1155` + * @dev Equivalent to keccak256(abi.encodePacked(rights, from, to, contract_, tokenId)) left-shifted with the last byte overwritten with ERC1155_TYPE + * @dev Will not revert if `from`, `to` or `contract_` are > uint160, these inputs will be cleaned to their lower 20 bytes + * @param from The address making the delegation + * @param rights The rights specified by the delegation + * @param to The address receiving the delegation + * @param tokenId The id of the ERC1155 token + * @param contract_ The address of the ERC1155 token contract + * @return hash The parameters encoded with ERC1155_TYPE + */ + function erc1155Hash(address from, bytes32 rights, address to, uint256 tokenId, address contract_) internal pure returns (bytes32 hash) { + assembly ("memory-safe") { + // This block only allocates memory after the free memory pointer + let ptr := mload(64) // Load the free memory pointer. + // Lay out the variables from last to first, agnostic to upper 96 bits of address words. + mstore(add(ptr, 92), tokenId) + mstore(add(ptr, 60), contract_) + mstore(add(ptr, 40), to) + mstore(add(ptr, 20), from) + mstore(ptr, rights) + hash := or(shl(8, keccak256(ptr, 124)), ERC1155_TYPE) // Keccak-hashes the packed encoding, left-shifts by one byte, then writes type to the lowest-order byte + } + } + + /** + * @notice Helper function to compute delegation location for `DelegationType.ERC1155` + * @dev Equivalent to `location(ERC1155Hash(rights, from, to, contract_, tokenId))` + * @dev Will not revert if `from`, `to` or `contract_` are > uint160, these inputs will be cleaned to their lower 20 bytes + * @param from The address making the delegation + * @param rights The rights specified by the delegation + * @param to The address receiving the delegation + * @param tokenId The id of the ERC1155 token + * @param contract_ The address of the ERC1155 token contract + * @return computedLocation The storage location of the ERC1155 delegation with those parameters in the delegations mapping + */ + function erc1155Location(address from, bytes32 rights, address to, uint256 tokenId, address contract_) internal pure returns (bytes32 computedLocation) { + assembly ("memory-safe") { + // This block only allocates memory after the free memory pointer and in the scratch space + let ptr := mload(64) // Cache the free memory pointer. + // Lay out the variables from last to first, agnostic to upper 96 bits of address words. + mstore(add(ptr, 92), tokenId) + mstore(add(ptr, 60), contract_) + mstore(add(ptr, 40), to) + mstore(add(ptr, 20), from) + mstore(ptr, rights) + mstore(0, or(shl(8, keccak256(ptr, 124)), ERC1155_TYPE)) // Computes erc1155Hash, then stores the result in scratch space + mstore(32, DELEGATION_SLOT) + computedLocation := keccak256(0, 64) // Runs keccak over the scratch space to obtain the storage key + } + } +} diff --git a/contracts/dependencies/delegation/libraries/RegistryOps.sol b/contracts/dependencies/delegation/libraries/RegistryOps.sol new file mode 100644 index 000000000..b59ade164 --- /dev/null +++ b/contracts/dependencies/delegation/libraries/RegistryOps.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: CC0-1.0 +pragma solidity ^0.8.21; + +library RegistryOps { + /// @dev `x > y ? x : y`. + function max(uint256 x, uint256 y) internal pure returns (uint256 z) { + assembly { + // `gt(y, x)` will evaluate to 1 if `y > x`, else 0. + // + // If `y > x`: + // `x ^ ((x ^ y) * 1) = x ^ (x ^ y) = (x ^ x) ^ y = 0 ^ y = y`. + // otherwise: + // `x ^ ((x ^ y) * 0) = x ^ 0 = x`. + z := xor(x, mul(xor(x, y), gt(y, x))) + } + } + + /// @dev `x & y`. + function and(bool x, bool y) internal pure returns (bool z) { + assembly { + z := and(iszero(iszero(x)), iszero(iszero(y))) // Compiler cleans dirty booleans on the stack to 1, so do the same here + } + } + + /// @dev `x | y`. + function or(bool x, bool y) internal pure returns (bool z) { + assembly { + z := or(iszero(iszero(x)), iszero(iszero(y))) // Compiler cleans dirty booleans on the stack to 1, so do the same here + } + } +} diff --git a/contracts/dependencies/delegation/libraries/RegistryStorage.sol b/contracts/dependencies/delegation/libraries/RegistryStorage.sol new file mode 100644 index 000000000..93e8f178e --- /dev/null +++ b/contracts/dependencies/delegation/libraries/RegistryStorage.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: CC0-1.0 +pragma solidity ^0.8.21; + +library RegistryStorage { + /// @dev Standardizes `from` storage flags to prevent double-writes in the delegation in/outbox if the same delegation is revoked and rewritten + address internal constant DELEGATION_EMPTY = address(0); + address internal constant DELEGATION_REVOKED = address(1); + + /// @dev Standardizes storage positions of delegation data + uint256 internal constant POSITIONS_FIRST_PACKED = 0; // | 4 bytes empty | first 8 bytes of contract address | 20 bytes of from address | + uint256 internal constant POSITIONS_SECOND_PACKED = 1; // | last 12 bytes of contract address | 20 bytes of to address | + uint256 internal constant POSITIONS_RIGHTS = 2; + uint256 internal constant POSITIONS_TOKEN_ID = 3; + uint256 internal constant POSITIONS_AMOUNT = 4; + + /// @dev Used to clean address types of dirty bits with `and(address, CLEAN_ADDRESS)` + uint256 internal constant CLEAN_ADDRESS = 0x00ffffffffffffffffffffffffffffffffffffffff; + + /// @dev Used to clean everything but the first 8 bytes of an address + uint256 internal constant CLEAN_FIRST8_BYTES_ADDRESS = 0xffffffffffffffff << 96; + + /// @dev Used to clean everything but the first 8 bytes of an address in the packed position + uint256 internal constant CLEAN_PACKED8_BYTES_ADDRESS = 0xffffffffffffffff << 160; + + /** + * @notice Helper function that packs from, to, and contract_ address to into the two slot configuration + * @param from The address making the delegation + * @param to The address receiving the delegation + * @param contract_ The contract address associated with the delegation (optional) + * @return firstPacked The firstPacked storage configured with the parameters + * @return secondPacked The secondPacked storage configured with the parameters + * @dev Will not revert if `from`, `to`, and `contract_` are > uint160, any inputs with dirty bits outside the last 20 bytes will be cleaned + */ + function packAddresses(address from, address to, address contract_) internal pure returns (bytes32 firstPacked, bytes32 secondPacked) { + assembly { + firstPacked := or(shl(64, and(contract_, CLEAN_FIRST8_BYTES_ADDRESS)), and(from, CLEAN_ADDRESS)) + secondPacked := or(shl(160, contract_), and(to, CLEAN_ADDRESS)) + } + } + + /** + * @notice Helper function that unpacks from, to, and contract_ address inside the firstPacked secondPacked storage configuration + * @param firstPacked The firstPacked storage to be decoded + * @param secondPacked The secondPacked storage to be decoded + * @return from The address making the delegation + * @return to The address receiving the delegation + * @return contract_ The contract address associated with the delegation + * @dev Will not revert if `from`, `to`, and `contract_` are > uint160, any inputs with dirty bits outside the last 20 bytes will be cleaned + */ + function unpackAddresses(bytes32 firstPacked, bytes32 secondPacked) internal pure returns (address from, address to, address contract_) { + assembly { + from := and(firstPacked, CLEAN_ADDRESS) + to := and(secondPacked, CLEAN_ADDRESS) + contract_ := or(shr(64, and(firstPacked, CLEAN_PACKED8_BYTES_ADDRESS)), shr(160, secondPacked)) + } + } + + /** + * @notice Helper function that can unpack the from or to address from their respective packed slots in the registry + * @param packedSlot The slot containing the from or to address + * @return unpacked The `from` or `to` address + * @dev Will not work if you want to obtain the contract address, use unpackAddresses + */ + function unpackAddress(bytes32 packedSlot) internal pure returns (address unpacked) { + assembly { + unpacked := and(packedSlot, CLEAN_ADDRESS) + } + } +} diff --git a/contracts/protocol/tokenization/NToken.sol b/contracts/protocol/tokenization/NToken.sol index da6faac9f..ad2592278 100644 --- a/contracts/protocol/tokenization/NToken.sol +++ b/contracts/protocol/tokenization/NToken.sol @@ -20,7 +20,6 @@ import {MintableIncentivizedERC721} from "./base/MintableIncentivizedERC721.sol" import {XTokenType} from "../../interfaces/IXTokenType.sol"; import {ITimeLock} from "../../interfaces/ITimeLock.sol"; import {ITokenDelegation} from "../../interfaces/ITokenDelegation.sol"; -import {IDelegationRegistry} from "../../dependencies/delegation/IDelegationRegistry.sol"; /** * @title ParaSpace ERC721 NToken diff --git a/contracts/protocol/tokenization/base/MintableIncentivizedERC721.sol b/contracts/protocol/tokenization/base/MintableIncentivizedERC721.sol index dd3fa1a6c..45abc5332 100644 --- a/contracts/protocol/tokenization/base/MintableIncentivizedERC721.sol +++ b/contracts/protocol/tokenization/base/MintableIncentivizedERC721.sol @@ -15,6 +15,7 @@ import {ICollateralizableERC721} from "../../../interfaces/ICollateralizableERC7 import {IAtomicCollateralizableERC721} from "../../../interfaces/IAtomicCollateralizableERC721.sol"; import {IAuctionableERC721} from "../../../interfaces/IAuctionableERC721.sol"; import {ITokenDelegation} from "../../../interfaces/ITokenDelegation.sol"; +import {IDelegateRegistry} from "../../../dependencies/delegation/IDelegateRegistry.sol"; import {IDelegationRegistry} from "../../../dependencies/delegation/IDelegationRegistry.sol"; import {SafeCast} from "../../../dependencies/openzeppelin/contracts/SafeCast.sol"; import {WadRayMath} from "../../libraries/math/WadRayMath.sol"; @@ -400,6 +401,10 @@ abstract contract MintableIncentivizedERC721 is ); } + function revokeDelegation(address v1Registry) external onlyPoolAdmin { + IDelegationRegistry(v1Registry).revokeAllDelegates(); + } + function delegateForToken( address delegate, uint256[] calldata tokenIds, diff --git a/contracts/protocol/tokenization/libraries/MintableERC721Logic.sol b/contracts/protocol/tokenization/libraries/MintableERC721Logic.sol index 0156dcfed..6819c26d2 100644 --- a/contracts/protocol/tokenization/libraries/MintableERC721Logic.sol +++ b/contracts/protocol/tokenization/libraries/MintableERC721Logic.sol @@ -12,7 +12,7 @@ import {SafeERC20} from "../../../dependencies/openzeppelin/contracts/SafeERC20. import {IERC20} from "../../../dependencies/openzeppelin/contracts/IERC20.sol"; import {IERC721} from "../../../dependencies/openzeppelin/contracts/IERC721.sol"; import {IERC1155} from "../../../dependencies/openzeppelin/contracts/IERC1155.sol"; -import {IDelegationRegistry} from "../../../dependencies/delegation/IDelegationRegistry.sol"; +import {IDelegateRegistry} from "../../../dependencies/delegation/IDelegateRegistry.sol"; struct UserState { uint64 balance; @@ -51,7 +51,10 @@ struct MintableERC721Data { address underlyingAsset; bool isTraitBoosted; mapping(uint256 => uint256) traitsMultipliers; + //replace old token delegation data + uint256 _placeHolder; mapping(uint256 => address) tokenDelegations; + uint256[50] __gap; } struct LocalVars { @@ -566,10 +569,11 @@ library MintableERC721Logic { delete erc721Data.tokenDelegations[tokenId]; } - IDelegationRegistry(delegationRegistry).delegateForToken( + IDelegateRegistry(delegationRegistry).delegateERC721( delegate, erc721Data.underlyingAsset, tokenId, + "", value ); } diff --git a/contracts/ui/UiPoolDataProvider.sol b/contracts/ui/UiPoolDataProvider.sol index f75f1dc63..540c23e0f 100644 --- a/contracts/ui/UiPoolDataProvider.sol +++ b/contracts/ui/UiPoolDataProvider.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.0; import {IERC20Detailed} from "../dependencies/openzeppelin/contracts/IERC20Detailed.sol"; import {IERC721Metadata} from "../dependencies/openzeppelin/contracts/IERC721Metadata.sol"; import {IERC721} from "../dependencies/openzeppelin/contracts/IERC721.sol"; -import {IDelegationRegistry} from "../dependencies/delegation/IDelegationRegistry.sol"; +import {IDelegateRegistry} from "../dependencies/delegation/IDelegateRegistry.sol"; import {IPoolAddressesProvider} from "../interfaces/IPoolAddressesProvider.sol"; import {IUiPoolDataProvider} from "./interfaces/IUiPoolDataProvider.sol"; import {IPool} from "../interfaces/IPool.sol"; @@ -554,21 +554,32 @@ contract UiPoolDataProvider is IUiPoolDataProvider { function getDelegatesForTokens( address vault, uint256[] calldata tokenIds - ) external view returns (DelegationData[] memory) { + ) external view returns (IDelegateRegistry.Delegation[] memory) { address contract_ = INToken(vault).UNDERLYING_ASSET_ADDRESS(); address delegationRegistry = ITokenDelegation(vault) .DELEGATE_REGISTRY(); - DelegationData[] memory delegationData = new DelegationData[]( - tokenIds.length - ); - - for (uint256 index = 0; index < tokenIds.length; index++) { - delegationData[index].delegations = IDelegationRegistry( - delegationRegistry - ).getDelegatesForToken(vault, contract_, tokenIds[index]); + IDelegateRegistry.Delegation[] memory delegations = IDelegateRegistry( + delegationRegistry + ).getOutgoingDelegations(vault); + + uint256 tokenLength = tokenIds.length; + IDelegateRegistry.Delegation[] + memory ret = new IDelegateRegistry.Delegation[](tokenLength); + uint256 delegationsLength = delegations.length; + for (uint256 index = 0; index < tokenLength; index++) { + for (uint256 j = 0; j < delegationsLength; j++) { + IDelegateRegistry.Delegation memory delegation = delegations[j]; + if ( + delegation.contract_ == contract_ && + delegation.tokenId == tokenIds[index] + ) { + ret[index] = delegation; + break; + } + } } - return delegationData; + return ret; } } diff --git a/contracts/ui/interfaces/IUiPoolDataProvider.sol b/contracts/ui/interfaces/IUiPoolDataProvider.sol index c496d8ae7..ee36af3c3 100644 --- a/contracts/ui/interfaces/IUiPoolDataProvider.sol +++ b/contracts/ui/interfaces/IUiPoolDataProvider.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.0; import {IPoolAddressesProvider} from "../../interfaces/IPoolAddressesProvider.sol"; import {ITimeLockStrategy} from "../../interfaces/ITimeLockStrategy.sol"; import {DataTypes} from "../../protocol/libraries/types/DataTypes.sol"; +import {IDelegateRegistry} from "../../dependencies/delegation/IDelegateRegistry.sol"; interface IUiPoolDataProvider { struct InterestRates { @@ -112,10 +113,6 @@ interface IUiPoolDataProvider { uint256 traitMultiplier; } - struct DelegationData { - address[] delegations; - } - function getReservesList( IPoolAddressesProvider provider ) external view returns (address[] memory); @@ -162,5 +159,5 @@ interface IUiPoolDataProvider { function getDelegatesForTokens( address vault, uint256[] calldata tokenIds - ) external view returns (DelegationData[] memory); + ) external view returns (IDelegateRegistry.Delegation[] memory); } diff --git a/helpers/contracts-deployments.ts b/helpers/contracts-deployments.ts index ebce79b16..b825dbb2f 100644 --- a/helpers/contracts-deployments.ts +++ b/helpers/contracts-deployments.ts @@ -31,7 +31,7 @@ import { DefaultReserveInterestRateStrategy, DefaultTimeLockStrategy, DelegationAwarePToken, - DelegationRegistry, + DelegateRegistry, DepositContract, Doodles, ERC20OracleWrapper, @@ -3178,11 +3178,11 @@ export const deployHotWalletProxy = async (verify?: boolean) => export const deployDelegationRegistry = async (verify?: boolean) => withSaveAndVerify( - await getContractFactory("DelegationRegistry"), + await getContractFactory("DelegateRegistry"), eContractid.DelegationRegistry, [], verify - ) as Promise; + ) as Promise; export const deployStakefishValidatorFactory = async ( genesisImplementation: tEthereumAddress, diff --git a/helpers/contracts-getters.ts b/helpers/contracts-getters.ts index be45e8662..693b99166 100644 --- a/helpers/contracts-getters.ts +++ b/helpers/contracts-getters.ts @@ -89,7 +89,7 @@ import { TimeLock__factory, HotWalletProxy__factory, NTokenOtherdeed__factory, - DelegationRegistry__factory, + DelegateRegistry__factory, DepositContract__factory, StakefishNFTManager__factory, StakefishValidatorV1__factory, @@ -1252,7 +1252,7 @@ export const getHotWalletProxy = async (address?: tEthereumAddress) => ); export const getDelegationRegistry = async (address?: tEthereumAddress) => - await DelegationRegistry__factory.connect( + await DelegateRegistry__factory.connect( address || ( await getDb() diff --git a/helpers/contracts-helpers.ts b/helpers/contracts-helpers.ts index 1e9e26c93..272aa7a7d 100644 --- a/helpers/contracts-helpers.ts +++ b/helpers/contracts-helpers.ts @@ -277,6 +277,17 @@ export const getTimeLockDataInDb = async (): Promise< ); }; +export const clearTimeLockDataInDb = async () => { + const key = `${eContractid.TimeLockExecutor}.${DRE.network.name}`; + const oldValue = (await getDb().get(key).value()) || {}; + const queue = []; + const newValue = { + ...oldValue, + queue, + }; + await getDb().set(key, newValue).write(); +}; + export const getContractAddressInDb = async (id: eContractid | string) => { return ((await getDb().get(`${id}.${DRE.network.name}`).value()) || {}) .address; @@ -1309,6 +1320,8 @@ export const linkLibraries = ( if (addr === undefined) { continue; } + console.log("****linkLibraries*****libName:", libName); + console.log("****linkLibraries*****addr:", addr); for (const fixup of fixups) { bytecode = diff --git a/helpers/hardhat-constants.ts b/helpers/hardhat-constants.ts index e0637a21e..ef66a0b05 100644 --- a/helpers/hardhat-constants.ts +++ b/helpers/hardhat-constants.ts @@ -223,7 +223,7 @@ export const MULTI_SEND_CHUNK_SIZE = parseInt( export const VERSION = version; export const COMMIT = git.short(); export const COMPILER_OPTIMIZER_RUNS = 200; -export const COMPILER_VERSION = "0.8.17+commit.8df45f5f"; +export const COMPILER_VERSION = "0.8.21"; export const PKG_DATA = { version: VERSION, git: { @@ -497,3 +497,6 @@ export const XTOKEN_TYPE_UPGRADE_WHITELIST = process.env.XTOKEN_TYPE_UPGRADE_WHITELIST?.trim() .split(/\s?,\s?/) .map((x) => +x); +export const XTOKEN_SYMBOL_UPGRADE_WHITELIST = + process.env.XTOKEN_SYMBOL_UPGRADE_WHITELIST?.trim() + .split(/\s?,\s?/); diff --git a/market-config/index.ts b/market-config/index.ts index 06ee039fa..5eecaa4cf 100644 --- a/market-config/index.ts +++ b/market-config/index.ts @@ -386,7 +386,7 @@ export const GoerliConfig: IParaSpaceConfiguration = { uBAYC: strategyuBAYC, uPPG: strategyuPPG, }, - DelegationRegistry: "0x00000000000076A84feF008CDAbe6409d2FE638B", + DelegationRegistry: "0x00000000000000447e69651d841bD8D104Bed493", }; export const PolygonConfig: IParaSpaceConfiguration = { @@ -997,7 +997,7 @@ export const MainnetConfig: IParaSpaceConfiguration = { Mocks: undefined, Oracle: MainnetOracleConfig, HotWallet: "0xC3AA9bc72Bd623168860a1e5c6a4530d3D80456c", - DelegationRegistry: "0x00000000000076A84feF008CDAbe6409d2FE638B", + DelegationRegistry: "0x00000000000000447e69651d841bD8D104Bed493", Governance: { Multisend: MULTI_SEND || "0x40A2aCCbd92BCA938b02010E17A5b8929b49130D", Multisig: MULTI_SIG || "0x19293FBec52F94165f903708a74513Dd6dFedd0a", diff --git a/scripts/upgrade/ntoken.ts b/scripts/upgrade/ntoken.ts index 1f1cb2e30..ba3ca36f6 100644 --- a/scripts/upgrade/ntoken.ts +++ b/scripts/upgrade/ntoken.ts @@ -24,6 +24,7 @@ import dotenv from "dotenv"; import { DRY_RUN, GLOBAL_OVERRIDES, + XTOKEN_SYMBOL_UPGRADE_WHITELIST, XTOKEN_TYPE_UPGRADE_WHITELIST, } from "../../helpers/hardhat-constants"; import {dryRunEncodedData} from "../../helpers/contracts-helpers"; @@ -75,6 +76,12 @@ export const upgradeNToken = async (verify = false) => { continue; } + if (XTOKEN_SYMBOL_UPGRADE_WHITELIST && !XTOKEN_SYMBOL_UPGRADE_WHITELIST.includes(symbol)) { + console.log(symbol + "not in XTOKEN_SYMBOL_UPGRADE_WHITELIST, skip..."); + continue; + } + + if (xTokenType == XTokenType.NTokenBAYC) { if (!nTokenBAYCImplementationAddress) { console.log("deploy NTokenBAYC implementation"); diff --git a/tasks/dev/timeLock.ts b/tasks/dev/timeLock.ts index f9222bd6a..edcec190c 100644 --- a/tasks/dev/timeLock.ts +++ b/tasks/dev/timeLock.ts @@ -203,6 +203,28 @@ task("decode-buffered-txs", "Decode buffered transactions").setAction( } ); +task("renew-buffered-txs", "Renew buffered transactions").setAction( + async (_, DRE) => { + await DRE.run("set-DRE"); + const { + getTimeLockDataInDb, + clearTimeLockDataInDb, + dryRunMultipleEncodedData, + } = await import("../../helpers/contracts-helpers"); + const actions = await getTimeLockDataInDb(); + + const targets: string[] = []; + const datas: string[] = []; + for (const a of actions) { + const [target, , , data] = a.action; + targets.push(target); + datas.push(data.toString()); + } + await clearTimeLockDataInDb(); + await dryRunMultipleEncodedData(targets, datas, []); + } +); + task("list-buffered-txs", "List buffered transactions").setAction( async (_, DRE) => { await DRE.run("set-DRE"); diff --git a/test/_xtoken_ntoken_delegation.ts b/test/_xtoken_ntoken_delegation.ts index 4389ab35b..67f9ca1c9 100644 --- a/test/_xtoken_ntoken_delegation.ts +++ b/test/_xtoken_ntoken_delegation.ts @@ -31,42 +31,32 @@ describe("NToken general", async () => { .connect(user1.signer) .delegateForToken(user2.address, ["0"], true); - await expect( - ( - await delegationRegistry.getDelegatesForToken( - nBAYC.address, - bayc.address, - "0" - ) - )[0] - ).to.be.eq(user2.address); + const delegation = await delegationRegistry.getOutgoingDelegations( + nBAYC.address + ); + expect(delegation[0].to).to.be.eq(user2.address); + expect(delegation[0].tokenId).to.be.eq(0); }); it("TC-ntoken-delegation-02: Non-Owner of NToken can not delegate to a given address", async () => { const { nBAYC, - bayc, users: [user1, user2], } = testEnv; await expect( nBAYC.connect(user2.signer).delegateForToken(user1.address, ["0"], true) ).to.be.revertedWith(ProtocolErrors.NOT_THE_OWNER); - await expect( - ( - await delegationRegistry.getDelegatesForToken( - nBAYC.address, - bayc.address, - "0" - ) - )[0] - ).to.be.eq(user2.address); + const delegation = await delegationRegistry.getOutgoingDelegations( + nBAYC.address + ); + expect(delegation[0].to).to.be.eq(user2.address); + expect(delegation[0].tokenId).to.be.eq(0); }); it("TC-ntoken-delegation-03: Owner of NToken can revoke delegation of a given address", async () => { const { nBAYC, - bayc, users: [user1, user2], } = testEnv; @@ -74,13 +64,8 @@ describe("NToken general", async () => { .connect(user1.signer) .delegateForToken(user2.address, ["0"], false); - await expect( - await delegationRegistry.getDelegatesForToken( - nBAYC.address, - bayc.address, - "0" - ) - ).to.be.empty; + expect(await delegationRegistry.getOutgoingDelegations(nBAYC.address)).to.be + .empty; }); it("TC-ntoken-delegation-04: Delegation status after resupplying the same token by different user", async () => { @@ -108,19 +93,15 @@ describe("NToken general", async () => { "0x0" ); - const delegatesAfter = await delegationRegistry.getDelegatesForToken( - nBAYC.address, - bayc.address, - "0" + const delegatesAfter = await delegationRegistry.getOutgoingDelegations( + nBAYC.address ); - - await expect(delegatesAfter).to.be.empty; + expect(delegatesAfter).to.be.empty; }); it("TC-ntoken-delegation-05: Delegation status after transferring the ntoken", async () => { const { nBAYC, - bayc, users: [user1, user2, user3], } = testEnv; @@ -136,19 +117,15 @@ describe("NToken general", async () => { "0" ); - const delegatesAfter = await delegationRegistry.getDelegatesForToken( - nBAYC.address, - bayc.address, - "0" + const delegatesAfter = await delegationRegistry.getOutgoingDelegations( + nBAYC.address ); - - await expect(delegatesAfter).to.be.empty; + expect(delegatesAfter).to.be.empty; }); - it("TC-ntoken-delegation-06: UI Provider can reterive delegation data for multiple tokens", async () => { + it("TC-ntoken-delegation-06: UI Provider can retrieve delegation data for multiple tokens", async () => { const { nBAYC, - bayc, users: [user1, user2], } = testEnv; @@ -158,16 +135,14 @@ describe("NToken general", async () => { const uiProvider = await getUiPoolDataProvider(); - const delegatesForToken = await delegationRegistry.getDelegatesForToken( - nBAYC.address, - bayc.address, - "0" + const delegatesForToken = await delegationRegistry.getOutgoingDelegations( + nBAYC.address ); const delegations = await uiProvider.getDelegatesForTokens(nBAYC.address, [ "0", ]); - await expect(delegatesForToken).to.be.eql(delegations[0].delegations); + expect(delegatesForToken[0]).to.be.eql(delegations[0]); }); }); diff --git a/typos.toml b/typos.toml index 326c5998f..a99fe8f29 100644 --- a/typos.toml +++ b/typos.toml @@ -2,6 +2,7 @@ extend-exclude = ["contracts/dependencies", "contracts/mocks", "lib/", "app/"] [default.extend-words] -BAKC = "BAKC" -BRE = "BRE" -Vas = "Vas" +BAKC = "BAKC" +BRE = "BRE" +Datas = "Datas" +Vas = "Vas" From 9f8ed4c1cc3a7f15a2d859256a17c8793293046e Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Mon, 11 Dec 2023 11:52:15 +0800 Subject: [PATCH 5/5] chore: cross chain struct implementation --- contracts/cross-chain/BridgeDefine.sol | 31 +++++++ .../cross-chain/L1/IParaxBridgeNFTVault.sol | 12 +++ .../cross-chain/L1/IParaxL1MessageHandler.sol | 10 +++ .../cross-chain/L1/ParaxBridgeNFTVault.sol | 86 +++++++++++++++++++ .../cross-chain/L1/ParaxL1MessageHandler.sol | 49 +++++++++++ contracts/cross-chain/L2/BridgeERC721.sol | 47 ++++++++++ .../cross-chain/L2/BridgeERC721Handler.sol | 34 ++++++++ contracts/cross-chain/L2/IBridgeERC721.sol | 8 ++ .../cross-chain/L2/IParaxL2MessageHandler.sol | 12 +++ .../cross-chain/L2/ParaxL2MessageHandler.sol | 38 ++++++++ contracts/interfaces/IPool.sol | 4 +- contracts/interfaces/IPoolCrossChain.sol | 20 +++++ contracts/interfaces/ITokenDelegation.sol | 6 -- contracts/mocks/upgradeability/MockNToken.sol | 2 +- .../protocol/libraries/helpers/Errors.sol | 6 ++ contracts/protocol/tokenization/NToken.sol | 6 +- .../tokenization/NTokenApeStaking.sol | 6 +- .../protocol/tokenization/NTokenBAKC.sol | 5 +- .../protocol/tokenization/NTokenBAYC.sol | 5 +- .../tokenization/NTokenChromieSquiggle.sol | 3 +- .../protocol/tokenization/NTokenMAYC.sol | 5 +- .../protocol/tokenization/NTokenMoonBirds.sol | 19 +--- .../protocol/tokenization/NTokenOtherdeed.sol | 44 ---------- .../protocol/tokenization/NTokenStakefish.sol | 5 +- .../protocol/tokenization/NTokenUniswapV3.sol | 5 +- .../base/MintableIncentivizedERC721.sol | 14 +-- .../libraries/MintableERC721Logic.sol | 36 +++----- contracts/ui/UiPoolDataProvider.sol | 32 ------- .../ui/interfaces/IUiPoolDataProvider.sol | 5 -- helpers/contracts-deployments.ts | 24 ------ helpers/contracts-getters.ts | 12 --- helpers/hardhat-constants.ts | 4 +- helpers/init-helpers.ts | 1 - helpers/types.ts | 1 - scripts/deployments/steps/11_allReserves.ts | 1 - scripts/upgrade/ntoken.ts | 6 +- 36 files changed, 388 insertions(+), 216 deletions(-) create mode 100644 contracts/cross-chain/BridgeDefine.sol create mode 100644 contracts/cross-chain/L1/IParaxBridgeNFTVault.sol create mode 100644 contracts/cross-chain/L1/IParaxL1MessageHandler.sol create mode 100644 contracts/cross-chain/L1/ParaxBridgeNFTVault.sol create mode 100644 contracts/cross-chain/L1/ParaxL1MessageHandler.sol create mode 100644 contracts/cross-chain/L2/BridgeERC721.sol create mode 100644 contracts/cross-chain/L2/BridgeERC721Handler.sol create mode 100644 contracts/cross-chain/L2/IBridgeERC721.sol create mode 100644 contracts/cross-chain/L2/IParaxL2MessageHandler.sol create mode 100644 contracts/cross-chain/L2/ParaxL2MessageHandler.sol create mode 100644 contracts/interfaces/IPoolCrossChain.sol delete mode 100644 contracts/protocol/tokenization/NTokenOtherdeed.sol diff --git a/contracts/cross-chain/BridgeDefine.sol b/contracts/cross-chain/BridgeDefine.sol new file mode 100644 index 000000000..cdf4ff6b5 --- /dev/null +++ b/contracts/cross-chain/BridgeDefine.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +enum MessageType { + AddNewCrossChainERC721, + BridgeERC721, + ERC721DELEGATION +} + +struct BridgeMessage { + MessageType msgType; + bytes data; +} + +struct BridgeERC721Message { + address asset; + uint256[] tokenIds; + address receiver; +} + +struct ERC721DelegationMessage { + address asset; + address delegateTo; + uint256[] tokenIds; + bool value; +} + +//library BridgeDefine { +// +// +//} diff --git a/contracts/cross-chain/L1/IParaxBridgeNFTVault.sol b/contracts/cross-chain/L1/IParaxBridgeNFTVault.sol new file mode 100644 index 000000000..e1bde7b26 --- /dev/null +++ b/contracts/cross-chain/L1/IParaxBridgeNFTVault.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {BridgeERC721Message, ERC721DelegationMessage} from "../BridgeDefine.sol"; + +interface IParaxBridgeNFTVault { + function releaseNFT(BridgeERC721Message calldata message) external; + + function updateTokenDelegation( + ERC721DelegationMessage calldata delegationInfo + ) external; +} diff --git a/contracts/cross-chain/L1/IParaxL1MessageHandler.sol b/contracts/cross-chain/L1/IParaxL1MessageHandler.sol new file mode 100644 index 000000000..a1d2d70c0 --- /dev/null +++ b/contracts/cross-chain/L1/IParaxL1MessageHandler.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {MessageType, BridgeMessage, BridgeERC721Message} from "../BridgeDefine.sol"; + +interface IParaxL1MessageHandler { + function addBridgeAsset(address asset) external; + + function bridgeAsset(BridgeERC721Message calldata message) external; +} diff --git a/contracts/cross-chain/L1/ParaxBridgeNFTVault.sol b/contracts/cross-chain/L1/ParaxBridgeNFTVault.sol new file mode 100644 index 000000000..64dde6ad2 --- /dev/null +++ b/contracts/cross-chain/L1/ParaxBridgeNFTVault.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {BridgeERC721Message, ERC721DelegationMessage} from "../BridgeDefine.sol"; +import {IERC721} from "../../dependencies/openzeppelin/contracts/IERC721.sol"; +import {Errors} from "../../protocol/libraries/helpers/Errors.sol"; +import "../../dependencies/openzeppelin/upgradeability/Initializable.sol"; +import "../../dependencies/openzeppelin/upgradeability/OwnableUpgradeable.sol"; +import "./IParaxL1MessageHandler.sol"; +import {IDelegateRegistry} from "../../dependencies/delegation/IDelegateRegistry.sol"; + +contract ParaxBridgeNFTVault is Initializable, OwnableUpgradeable { + IParaxL1MessageHandler internal immutable l1MsgHander; + + IDelegateRegistry delegationRegistry; + + mapping(address => bool) supportAsset; + + constructor(IParaxL1MessageHandler msgHandler) { + l1MsgHander = msgHandler; + } + + modifier onlyMsgHandler() { + require(msg.sender == address(l1MsgHander), Errors.ONLY_MSG_HANDLER); + _; + } + + function addBridgeAsset(address asset) external { + require(supportAsset[asset] == false, "asset already added"); + supportAsset[asset] = true; + l1MsgHander.addBridgeAsset(asset); + } + + function bridgeAsset( + address asset, + uint256[] calldata tokenIds, + address receiver + ) external { + require(supportAsset[asset] == true, "asset already added"); + //lock asset + uint256 length = tokenIds.length; + for (uint256 index = 0; index < length; index++) { + uint256 tokenId = tokenIds[index]; + IERC721(asset).safeTransferFrom(msg.sender, address(this), tokenId); + } + + //send cross chain msg + l1MsgHander.bridgeAsset( + BridgeERC721Message({ + asset: asset, + tokenIds: tokenIds, + receiver: receiver + }) + ); + } + + function releaseNFT( + BridgeERC721Message calldata message + ) external onlyMsgHandler { + uint256 length = message.tokenIds.length; + for (uint256 index = 0; index < length; index++) { + uint256 tokenId = message.tokenIds[index]; + IERC721(message.asset).safeTransferFrom( + address(this), + message.receiver, + tokenId + ); + } + } + + function updateTokenDelegation( + ERC721DelegationMessage calldata delegationInfo + ) external onlyMsgHandler { + uint256 length = delegationInfo.tokenIds.length; + for (uint256 index = 0; index < length; index++) { + uint256 tokenId = delegationInfo.tokenIds[index]; + delegationRegistry.delegateERC721( + delegationInfo.delegateTo, + delegationInfo.asset, + tokenId, + "", + delegationInfo.value + ); + } + } +} diff --git a/contracts/cross-chain/L1/ParaxL1MessageHandler.sol b/contracts/cross-chain/L1/ParaxL1MessageHandler.sol new file mode 100644 index 000000000..15825d338 --- /dev/null +++ b/contracts/cross-chain/L1/ParaxL1MessageHandler.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; +pragma abicoder v2; + +import {MessageType, BridgeMessage, BridgeERC721Message} from "../BridgeDefine.sol"; +import {Errors} from "../../protocol/libraries/helpers/Errors.sol"; +import "./IParaxBridgeNFTVault.sol"; + +contract ParaxL1MessageHandler { + IParaxBridgeNFTVault internal immutable nftVault; + address immutable bridgeImpl; + + constructor(IParaxBridgeNFTVault vault, address bridge) { + nftVault = vault; + bridgeImpl = bridge; + } + + modifier onlyVault() { + require(msg.sender == address(nftVault), Errors.ONLY_VAULT); + _; + } + + modifier onlyBridge() { + require(msg.sender == address(bridgeImpl), Errors.ONLY_BRIDGE); + _; + } + + function addBridgeAsset(address asset) external onlyVault {} + + function bridgeAsset( + BridgeERC721Message calldata message + ) external onlyVault {} + + function bridgeReceive(BridgeMessage calldata message) external onlyBridge { + if (message.msgType == MessageType.BridgeERC721) { + BridgeERC721Message memory message = abi.decode( + message.data, + (BridgeERC721Message) + ); + nftVault.releaseNFT(message); + } else if (message.msgType == MessageType.ERC721DELEGATION) { + ERC721DelegationMessage memory message = abi.decode( + message.data, + (ERC721DelegationMessage) + ); + nftVault.updateTokenDelegation(message); + } + } +} diff --git a/contracts/cross-chain/L2/BridgeERC721.sol b/contracts/cross-chain/L2/BridgeERC721.sol new file mode 100644 index 000000000..f8db4385b --- /dev/null +++ b/contracts/cross-chain/L2/BridgeERC721.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {ERC721} from "../../dependencies/openzeppelin/contracts/ERC721.sol"; +import {ERC721Enumerable} from "../../dependencies/openzeppelin/contracts/ERC721Enumerable.sol"; +import {Errors} from "../../protocol/libraries/helpers/Errors.sol"; + +contract BridgeERC21 is ERC721Enumerable { + address internal immutable handler; + + modifier onlyHandler() { + require(msg.sender == handler, Errors.ONLY_VAULT); + _; + } + + constructor( + string memory name, + string memory symbol, + address _handler + ) ERC721(name, symbol) { + handler = _handler; + } + + function mint( + address to, + uint256[] calldata tokenIds + ) external onlyHandler { + uint256 length = tokenIds.length; + for (uint256 index = 0; index < length; index++) { + uint256 tokenId = tokenIds[index]; + _mint(to, tokenId); + } + } + + function burn( + address from, + uint256[] calldata tokenIds + ) external onlyHandler { + uint256 length = tokenIds.length; + for (uint256 index = 0; index < length; index++) { + uint256 tokenId = tokenIds[index]; + address owner = ownerOf(tokenId); + require(owner == from, "invalid"); + _burn(tokenId); + } + } +} diff --git a/contracts/cross-chain/L2/BridgeERC721Handler.sol b/contracts/cross-chain/L2/BridgeERC721Handler.sol new file mode 100644 index 000000000..81fdc81bf --- /dev/null +++ b/contracts/cross-chain/L2/BridgeERC721Handler.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {MessageType, BridgeMessage, BridgeERC721Message} from "../BridgeDefine.sol"; +import {ERC721} from "../../dependencies/openzeppelin/contracts/ERC721.sol"; +import "./IParaxL2MessageHandler.sol"; +import "./IBridgeERC721.sol"; +import {Errors} from "../../protocol/libraries/helpers/Errors.sol"; + +contract BridgeERC21Handler { + IParaxL2MessageHandler internal immutable l2MsgHandler; + + //origin asset -> bridge asset + mapping(address => address) getBridgeAsset; + mapping(address => address) getOriginAsset; + + constructor(IParaxL2MessageHandler msgHandler) { + l2MsgHandler = msgHandler; + } + + modifier onlyMsgHandler() { + require(msg.sender == address(l2MsgHandler), Errors.ONLY_HANDLER); + _; + } + + function bridgeAsset( + BridgeERC721Message calldata message + ) external onlyMsgHandler { + address bridgeAsset = getBridgeAsset[message.asset]; + require(bridgeAsset != address(0), "invalid"); + + IBridgeERC721(bridgeAsset).mint(message.receiver, message.tokenIds); + } +} diff --git a/contracts/cross-chain/L2/IBridgeERC721.sol b/contracts/cross-chain/L2/IBridgeERC721.sol new file mode 100644 index 000000000..327eb7b51 --- /dev/null +++ b/contracts/cross-chain/L2/IBridgeERC721.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IBridgeERC721 { + function mint(address to, uint256[] calldata tokenId) external; + + function burn(address from, uint256[] calldata tokenId) external; +} diff --git a/contracts/cross-chain/L2/IParaxL2MessageHandler.sol b/contracts/cross-chain/L2/IParaxL2MessageHandler.sol new file mode 100644 index 000000000..d2f4a1904 --- /dev/null +++ b/contracts/cross-chain/L2/IParaxL2MessageHandler.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {MessageType, BridgeMessage, BridgeERC721Message, ERC721DelegationMessage} from "../BridgeDefine.sol"; + +interface IParaxL2MessageHandler { + //function bridgeAsset(BridgeERC721Message calldata message) external; + + function updateTokenDelegation( + ERC721DelegationMessage calldata delegationInfo + ) external; +} diff --git a/contracts/cross-chain/L2/ParaxL2MessageHandler.sol b/contracts/cross-chain/L2/ParaxL2MessageHandler.sol new file mode 100644 index 000000000..216c74657 --- /dev/null +++ b/contracts/cross-chain/L2/ParaxL2MessageHandler.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {MessageType, BridgeMessage} from "../BridgeDefine.sol"; +import "./BridgeERC721Handler.sol"; +import "./IParaxL2MessageHandler.sol"; + +contract ParaxL2MessageHandler is IParaxL2MessageHandler { + BridgeERC21Handler internal immutable erc712Handler; + address immutable bridgeImpl; + address immutable paraX; + + constructor(BridgeERC21Handler handler) { + erc712Handler = handler; + } + + function bridgeReceive(BridgeMessage calldata message) external { + require(msg.sender == bridgeImpl, ""); + if (message.msgType == MessageType.BridgeERC721) { + BridgeERC721Message memory message = abi.decode( + message.data, + (BridgeERC721Message) + ); + erc712Handler.bridgeAsset(message); + } else {} + } + + function updateTokenDelegation( + ERC721DelegationMessage calldata delegationInfo + ) external { + require(msg.sender == paraX, Errors.ONLY_PARAX); + + BridgeMessage memory message; + message.msgType = MessageType.ERC721DELEGATION; + message.data = abi.encode(delegationInfo); + //send msg + } +} diff --git a/contracts/interfaces/IPool.sol b/contracts/interfaces/IPool.sol index f099ff026..d326fc614 100644 --- a/contracts/interfaces/IPool.sol +++ b/contracts/interfaces/IPool.sol @@ -9,6 +9,7 @@ import {IPoolPositionMover} from "./IPoolPositionMover.sol"; import {IPoolAAPositionMover} from "./IPoolAAPositionMover.sol"; import "./IPoolApeStaking.sol"; import "./IPoolBorrowAndStake.sol"; +import "./IPoolCrossChain.sol"; /** * @title IPool @@ -23,7 +24,8 @@ interface IPool is IParaProxyInterfaces, IPoolPositionMover, IPoolBorrowAndStake, - IPoolAAPositionMover + IPoolAAPositionMover, + IPoolCrossChain { } diff --git a/contracts/interfaces/IPoolCrossChain.sol b/contracts/interfaces/IPoolCrossChain.sol new file mode 100644 index 000000000..d225b6b25 --- /dev/null +++ b/contracts/interfaces/IPoolCrossChain.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity ^0.8.0; + +import {DataTypes} from "../protocol/libraries/types/DataTypes.sol"; + +/** + * @title IPool + * + * @notice Defines the basic interface for an ParaSpace Pool. + **/ +interface IPoolCrossChain { + function updateTokenDelegation( + address delegateTo, + address underlyingAsset, + uint256[] calldata tokenIds, + bool value + ) external; + + function CROSS_CHAIN_MSG_HANDLER() external view returns (address); +} diff --git a/contracts/interfaces/ITokenDelegation.sol b/contracts/interfaces/ITokenDelegation.sol index c240023ec..2b8539ebe 100644 --- a/contracts/interfaces/ITokenDelegation.sol +++ b/contracts/interfaces/ITokenDelegation.sol @@ -13,10 +13,4 @@ interface ITokenDelegation { uint256[] calldata tokenIds, bool value ) external; - - /** - * @notice Returns the address of the delegation registry of this nToken - * @return The address of the delegation registry - **/ - function DELEGATE_REGISTRY() external view returns (address); } diff --git a/contracts/mocks/upgradeability/MockNToken.sol b/contracts/mocks/upgradeability/MockNToken.sol index e3477eeb2..707a7146e 100644 --- a/contracts/mocks/upgradeability/MockNToken.sol +++ b/contracts/mocks/upgradeability/MockNToken.sol @@ -6,7 +6,7 @@ import {IPool} from "../../interfaces/IPool.sol"; import {IRewardController} from "../../interfaces/IRewardController.sol"; contract MockNToken is NToken { - constructor(IPool pool, address delegateRegistry) NToken(pool, false, delegateRegistry) {} + constructor(IPool pool) NToken(pool, false) {} function getRevision() internal pure override returns (uint256) { return 999; diff --git a/contracts/protocol/libraries/helpers/Errors.sol b/contracts/protocol/libraries/helpers/Errors.sol index ece1ab752..55f86e966 100644 --- a/contracts/protocol/libraries/helpers/Errors.sol +++ b/contracts/protocol/libraries/helpers/Errors.sol @@ -136,4 +136,10 @@ library Errors { string public constant INVALID_PARAMETER = "170"; //invalid parameter string public constant INVALID_CALLER = "171"; //invalid callser + + string public constant ONLY_MSG_HANDLER = "200"; //only msg handler + string public constant ONLY_VAULT = "201"; //only vault + string public constant ONLY_HANDLER = "202"; //only handler + string public constant ONLY_PARAX = "203"; //only parax + string public constant ONLY_BRIDGE = "204"; //only cross-chain bridge } diff --git a/contracts/protocol/tokenization/NToken.sol b/contracts/protocol/tokenization/NToken.sol index ad2592278..98defb89e 100644 --- a/contracts/protocol/tokenization/NToken.sol +++ b/contracts/protocol/tokenization/NToken.sol @@ -42,15 +42,13 @@ contract NToken is VersionedInitializable, MintableIncentivizedERC721, INToken { */ constructor( IPool pool, - bool atomic_pricing, - address delegateRegistry + bool atomic_pricing ) MintableIncentivizedERC721( pool, "NTOKEN_IMPL", "NTOKEN_IMPL", - atomic_pricing, - delegateRegistry + atomic_pricing ) {} diff --git a/contracts/protocol/tokenization/NTokenApeStaking.sol b/contracts/protocol/tokenization/NTokenApeStaking.sol index 1fbe8a6ce..55dbaae97 100644 --- a/contracts/protocol/tokenization/NTokenApeStaking.sol +++ b/contracts/protocol/tokenization/NTokenApeStaking.sol @@ -35,11 +35,7 @@ abstract contract NTokenApeStaking is NToken, INTokenApeStaking { * @dev Constructor. * @param pool The address of the Pool contract */ - constructor( - IPool pool, - address apeCoinStaking, - address delegateRegistry - ) NToken(pool, false, delegateRegistry) { + constructor(IPool pool, address apeCoinStaking) NToken(pool, false) { _apeCoinStaking = ApeCoinStaking(apeCoinStaking); } diff --git a/contracts/protocol/tokenization/NTokenBAKC.sol b/contracts/protocol/tokenization/NTokenBAKC.sol index 31432b406..18a31cff0 100644 --- a/contracts/protocol/tokenization/NTokenBAKC.sol +++ b/contracts/protocol/tokenization/NTokenBAKC.sol @@ -32,9 +32,8 @@ contract NTokenBAKC is NToken { IPool pool, address apeCoinStaking, address _nBAYC, - address _nMAYC, - address delegateRegistry - ) NToken(pool, false, delegateRegistry) { + address _nMAYC + ) NToken(pool, false) { _apeCoinStaking = ApeCoinStaking(apeCoinStaking); nBAYC = _nBAYC; nMAYC = _nMAYC; diff --git a/contracts/protocol/tokenization/NTokenBAYC.sol b/contracts/protocol/tokenization/NTokenBAYC.sol index 55fd6cf69..c87ab35a0 100644 --- a/contracts/protocol/tokenization/NTokenBAYC.sol +++ b/contracts/protocol/tokenization/NTokenBAYC.sol @@ -15,9 +15,8 @@ import {ApeStakingLogic} from "./libraries/ApeStakingLogic.sol"; contract NTokenBAYC is NTokenApeStaking { constructor( IPool pool, - address apeCoinStaking, - address delegateRegistry - ) NTokenApeStaking(pool, apeCoinStaking, delegateRegistry) {} + address apeCoinStaking + ) NTokenApeStaking(pool, apeCoinStaking) {} /** * @notice Deposit ApeCoin to the BAYC Pool diff --git a/contracts/protocol/tokenization/NTokenChromieSquiggle.sol b/contracts/protocol/tokenization/NTokenChromieSquiggle.sol index f555e9558..2e6b1639f 100644 --- a/contracts/protocol/tokenization/NTokenChromieSquiggle.sol +++ b/contracts/protocol/tokenization/NTokenChromieSquiggle.sol @@ -32,10 +32,9 @@ contract NTokenChromieSquiggle is NToken { */ constructor( IPool pool, - address delegateRegistry, uint256 _startTokenId, uint256 _endTokenId - ) NToken(pool, false, delegateRegistry) { + ) NToken(pool, false) { startTokenId = _startTokenId; endTokenId = _endTokenId; } diff --git a/contracts/protocol/tokenization/NTokenMAYC.sol b/contracts/protocol/tokenization/NTokenMAYC.sol index 780a67c91..5c4be8f08 100644 --- a/contracts/protocol/tokenization/NTokenMAYC.sol +++ b/contracts/protocol/tokenization/NTokenMAYC.sol @@ -15,9 +15,8 @@ import {ApeStakingLogic} from "./libraries/ApeStakingLogic.sol"; contract NTokenMAYC is NTokenApeStaking { constructor( IPool pool, - address apeCoinStaking, - address delegateRegistry - ) NTokenApeStaking(pool, apeCoinStaking, delegateRegistry) {} + address apeCoinStaking + ) NTokenApeStaking(pool, apeCoinStaking) {} /** * @notice Deposit ApeCoin to the MAYC Pool diff --git a/contracts/protocol/tokenization/NTokenMoonBirds.sol b/contracts/protocol/tokenization/NTokenMoonBirds.sol index fc24a298e..c069fc9b8 100644 --- a/contracts/protocol/tokenization/NTokenMoonBirds.sol +++ b/contracts/protocol/tokenization/NTokenMoonBirds.sol @@ -26,19 +26,11 @@ import {ITimeLock} from "../../interfaces/ITimeLock.sol"; * @notice Implementation of the interest bearing token for the ParaSpace protocol */ contract NTokenMoonBirds is NToken, IMoonBirdBase { - address internal immutable timeLockV1; - /** * @dev Constructor. * @param pool The address of the Pool contract */ - constructor( - IPool pool, - address delegateRegistry, - address _timeLockV1 - ) NToken(pool, false, delegateRegistry) { - timeLockV1 = _timeLockV1; - } + constructor(IPool pool) NToken(pool, false) {} function getXTokenType() external pure override returns (XTokenType) { return XTokenType.NTokenMoonBirds; @@ -98,7 +90,7 @@ contract NTokenMoonBirds is NToken, IMoonBirdBase { ) external virtual override returns (bytes4) { // if the operator is the pool, this means that the pool is transferring the token to this contract // which can happen during a normal supplyERC721 pool tx - if (operator == address(POOL) || operator == timeLockV1) { + if (operator == address(POOL)) { return this.onERC721Received.selector; } @@ -154,11 +146,4 @@ contract NTokenMoonBirds is NToken, IMoonBirdBase { function nestingOpen() external view returns (bool) { return IMoonBird(_ERC721Data.underlyingAsset).nestingOpen(); } - - function claimUnderlying( - address timeLockV1, - uint256[] calldata agreementIds - ) external virtual override onlyPool { - ITimeLock(timeLockV1).claimMoonBirds(agreementIds); - } } diff --git a/contracts/protocol/tokenization/NTokenOtherdeed.sol b/contracts/protocol/tokenization/NTokenOtherdeed.sol deleted file mode 100644 index ab6933c40..000000000 --- a/contracts/protocol/tokenization/NTokenOtherdeed.sol +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import {IHotWalletProxy} from "../../interfaces/IHotWalletProxy.sol"; -import {NToken} from "./NToken.sol"; -import {IPool} from "../../interfaces/IPool.sol"; -import {XTokenType} from "../../interfaces/IXTokenType.sol"; - -/** - * @title Otherdeed NToken - * - * @notice Implementation of the interest bearing token for the ParaSpace protocol - */ -contract NTokenOtherdeed is NToken, IHotWalletProxy { - IHotWalletProxy private immutable WARM_WALLET; - - /** - * @dev Constructor. - * @param pool The address of the Pool contract - */ - constructor( - IPool pool, - IHotWalletProxy warmWallet, - address delegateRegistry - ) NToken(pool, false, delegateRegistry) { - WARM_WALLET = warmWallet; - } - - function setHotWallet( - address hotWalletAddress, - uint256 expirationTimestamp, - bool lockHotWalletAddress - ) external onlyPoolAdmin { - WARM_WALLET.setHotWallet( - hotWalletAddress, - expirationTimestamp, - lockHotWalletAddress - ); - } - - function getXTokenType() external pure override returns (XTokenType) { - return XTokenType.NTokenOtherdeed; - } -} diff --git a/contracts/protocol/tokenization/NTokenStakefish.sol b/contracts/protocol/tokenization/NTokenStakefish.sol index 92e302a24..73a2cb88f 100644 --- a/contracts/protocol/tokenization/NTokenStakefish.sol +++ b/contracts/protocol/tokenization/NTokenStakefish.sol @@ -26,10 +26,7 @@ contract NTokenStakefish is NToken, INTokenStakefish { * @dev Constructor. * @param pool The address of the Pool contract */ - constructor( - IPool pool, - address delegateRegistry - ) NToken(pool, false, delegateRegistry) { + constructor(IPool pool) NToken(pool, false) { WETH = IWETH(_addressesProvider.getWETH()); } diff --git a/contracts/protocol/tokenization/NTokenUniswapV3.sol b/contracts/protocol/tokenization/NTokenUniswapV3.sol index a7cc8cfc6..573dfccf6 100644 --- a/contracts/protocol/tokenization/NTokenUniswapV3.sol +++ b/contracts/protocol/tokenization/NTokenUniswapV3.sol @@ -31,10 +31,7 @@ contract NTokenUniswapV3 is NToken, INTokenUniswapV3 { * @dev Constructor. * @param pool The address of the Pool contract */ - constructor( - IPool pool, - address delegateRegistry - ) NToken(pool, true, delegateRegistry) { + constructor(IPool pool) NToken(pool, true) { _ERC721Data.balanceLimit = 30; } diff --git a/contracts/protocol/tokenization/base/MintableIncentivizedERC721.sol b/contracts/protocol/tokenization/base/MintableIncentivizedERC721.sol index 45abc5332..d47964476 100644 --- a/contracts/protocol/tokenization/base/MintableIncentivizedERC721.sol +++ b/contracts/protocol/tokenization/base/MintableIncentivizedERC721.sol @@ -91,7 +91,6 @@ abstract contract MintableIncentivizedERC721 is IPoolAddressesProvider internal immutable _addressesProvider; IPool internal immutable POOL; bool internal immutable ATOMIC_PRICING; - address internal immutable DELEGATE_REGISTRY_ADDRESS; /** * @dev Constructor. @@ -103,15 +102,13 @@ abstract contract MintableIncentivizedERC721 is IPool pool, string memory name_, string memory symbol_, - bool atomic_pricing, - address delegateRegistry + bool atomic_pricing ) { _addressesProvider = pool.ADDRESSES_PROVIDER(); _ERC721Data.name = name_; _ERC721Data.symbol = symbol_; POOL = pool; ATOMIC_PRICING = atomic_pricing; - DELEGATE_REGISTRY_ADDRESS = delegateRegistry; } function name() public view override returns (string memory) { @@ -395,7 +392,6 @@ abstract contract MintableIncentivizedERC721 is _ERC721Data, POOL, ATOMIC_PRICING, - DELEGATE_REGISTRY_ADDRESS, user, tokenIds ); @@ -424,7 +420,7 @@ abstract contract MintableIncentivizedERC721 is MintableERC721Logic.executeUpdateTokenDelegation( _ERC721Data, - DELEGATE_REGISTRY_ADDRESS, + POOL, delegate, tokenIds[index], value @@ -432,10 +428,6 @@ abstract contract MintableIncentivizedERC721 is } } - function DELEGATE_REGISTRY() external view returns (address) { - return DELEGATE_REGISTRY_ADDRESS; - } - /** * @dev Transfers `tokenId` from `from` to `to`. * As opposed to {transferFrom}, this imposes no restrictions on msg.sender. @@ -456,7 +448,6 @@ abstract contract MintableIncentivizedERC721 is _ERC721Data, POOL, ATOMIC_PRICING, - DELEGATE_REGISTRY_ADDRESS, from, to, tokenId @@ -476,7 +467,6 @@ abstract contract MintableIncentivizedERC721 is _ERC721Data, POOL, ATOMIC_PRICING, - DELEGATE_REGISTRY_ADDRESS, from, to, tokenId diff --git a/contracts/protocol/tokenization/libraries/MintableERC721Logic.sol b/contracts/protocol/tokenization/libraries/MintableERC721Logic.sol index 6819c26d2..d4de32c4c 100644 --- a/contracts/protocol/tokenization/libraries/MintableERC721Logic.sol +++ b/contracts/protocol/tokenization/libraries/MintableERC721Logic.sol @@ -172,7 +172,6 @@ library MintableERC721Logic { MintableERC721Data storage erc721Data, IPool POOL, bool ATOMIC_PRICING, - address DELEGATION_REGISTRY, address from, address to, uint256 tokenId @@ -208,7 +207,7 @@ library MintableERC721Logic { if (from != to && tokenDelegationAddress != address(0)) { _updateTokenDelegation( erc721Data, - DELEGATION_REGISTRY, + POOL, tokenDelegationAddress, tokenId, false @@ -239,7 +238,6 @@ library MintableERC721Logic { MintableERC721Data storage erc721Data, IPool POOL, bool ATOMIC_PRICING, - address DELEGATION_REGISTRY, address from, address to, uint256 tokenId @@ -260,15 +258,7 @@ library MintableERC721Logic { delete erc721Data.isUsedAsCollateral[tokenId]; } - executeTransfer( - erc721Data, - POOL, - ATOMIC_PRICING, - DELEGATION_REGISTRY, - from, - to, - tokenId - ); + executeTransfer(erc721Data, POOL, ATOMIC_PRICING, from, to, tokenId); } function executeSetIsUsedAsCollateral( @@ -441,7 +431,6 @@ library MintableERC721Logic { MintableERC721Data storage erc721Data, IPool POOL, bool ATOMIC_PRICING, - address DELEGATION_REGISTRY, address user, uint256[] calldata tokenIds ) external returns (uint64, uint64) { @@ -496,7 +485,7 @@ library MintableERC721Logic { if (tokenDelegationAddress != address(0)) { _updateTokenDelegation( erc721Data, - DELEGATION_REGISTRY, + POOL, tokenDelegationAddress, tokenIds[index], false @@ -542,23 +531,17 @@ library MintableERC721Logic { function executeUpdateTokenDelegation( MintableERC721Data storage erc721Data, - address delegationRegistry, + IPool POOL, address delegate, uint256 tokenId, bool value ) external { - _updateTokenDelegation( - erc721Data, - delegationRegistry, - delegate, - tokenId, - value - ); + _updateTokenDelegation(erc721Data, POOL, delegate, tokenId, value); } function _updateTokenDelegation( MintableERC721Data storage erc721Data, - address delegationRegistry, + IPool POOL, address delegate, uint256 tokenId, bool value @@ -569,11 +552,12 @@ library MintableERC721Logic { delete erc721Data.tokenDelegations[tokenId]; } - IDelegateRegistry(delegationRegistry).delegateERC721( + uint256[] memory tokenIds = new uint256[](1); + tokenIds[0] = tokenId; + POOL.updateTokenDelegation( delegate, erc721Data.underlyingAsset, - tokenId, - "", + tokenIds, value ); } diff --git a/contracts/ui/UiPoolDataProvider.sol b/contracts/ui/UiPoolDataProvider.sol index 540c23e0f..73a86b476 100644 --- a/contracts/ui/UiPoolDataProvider.sol +++ b/contracts/ui/UiPoolDataProvider.sol @@ -550,36 +550,4 @@ contract UiPoolDataProvider is IUiPoolDataProvider { } return (userData, tokensData); } - - function getDelegatesForTokens( - address vault, - uint256[] calldata tokenIds - ) external view returns (IDelegateRegistry.Delegation[] memory) { - address contract_ = INToken(vault).UNDERLYING_ASSET_ADDRESS(); - address delegationRegistry = ITokenDelegation(vault) - .DELEGATE_REGISTRY(); - - IDelegateRegistry.Delegation[] memory delegations = IDelegateRegistry( - delegationRegistry - ).getOutgoingDelegations(vault); - - uint256 tokenLength = tokenIds.length; - IDelegateRegistry.Delegation[] - memory ret = new IDelegateRegistry.Delegation[](tokenLength); - uint256 delegationsLength = delegations.length; - for (uint256 index = 0; index < tokenLength; index++) { - for (uint256 j = 0; j < delegationsLength; j++) { - IDelegateRegistry.Delegation memory delegation = delegations[j]; - if ( - delegation.contract_ == contract_ && - delegation.tokenId == tokenIds[index] - ) { - ret[index] = delegation; - break; - } - } - } - - return ret; - } } diff --git a/contracts/ui/interfaces/IUiPoolDataProvider.sol b/contracts/ui/interfaces/IUiPoolDataProvider.sol index ee36af3c3..194c0c45c 100644 --- a/contracts/ui/interfaces/IUiPoolDataProvider.sol +++ b/contracts/ui/interfaces/IUiPoolDataProvider.sol @@ -155,9 +155,4 @@ interface IUiPoolDataProvider { external view returns (UserGlobalData memory, TokenInLiquidationData[][] memory); - - function getDelegatesForTokens( - address vault, - uint256[] calldata tokenIds - ) external view returns (IDelegateRegistry.Delegation[] memory); } diff --git a/helpers/contracts-deployments.ts b/helpers/contracts-deployments.ts index b825dbb2f..5019ae3f1 100644 --- a/helpers/contracts-deployments.ts +++ b/helpers/contracts-deployments.ts @@ -3096,30 +3096,6 @@ export const deployReserveTimeLockStrategy = async ( verify ) as Promise; -export const deployOtherdeedNTokenImpl = async ( - poolAddress: tEthereumAddress, - warmWallet: tEthereumAddress, - delegationRegistryAddress: tEthereumAddress, - verify?: boolean -) => { - const mintableERC721Logic = - (await getContractAddressInDb(eContractid.MintableERC721Logic)) || - (await deployMintableERC721Logic(verify)).address; - - const libraries = { - ["contracts/protocol/tokenization/libraries/MintableERC721Logic.sol:MintableERC721Logic"]: - mintableERC721Logic, - }; - return withSaveAndVerify( - await getContractFactory("NTokenOtherdeed", libraries), - eContractid.NTokenOtherdeedImpl, - [poolAddress, warmWallet, delegationRegistryAddress], - verify, - false, - libraries - ) as Promise; -}; - export const deployChromieSquiggleNTokenImpl = async ( poolAddress: tEthereumAddress, delegationRegistryAddress: tEthereumAddress, diff --git a/helpers/contracts-getters.ts b/helpers/contracts-getters.ts index 693b99166..86f055b71 100644 --- a/helpers/contracts-getters.ts +++ b/helpers/contracts-getters.ts @@ -88,7 +88,6 @@ import { MockCToken__factory, TimeLock__factory, HotWalletProxy__factory, - NTokenOtherdeed__factory, DelegateRegistry__factory, DepositContract__factory, StakefishNFTManager__factory, @@ -1218,17 +1217,6 @@ export const getTimeLockProxy = async (address?: tEthereumAddress) => await getFirstSigner() ); -export const getNTokenOtherdeed = async (address?: tEthereumAddress) => - await NTokenOtherdeed__factory.connect( - address || - ( - await getDb() - .get(`${eContractid.NTokenOtherdeedImpl}.${DRE.network.name}`) - .value() - ).address, - await getFirstSigner() - ); - export const getNTokenChromieSquiggle = async (address?: tEthereumAddress) => await NTokenChromieSquiggle__factory.connect( address || diff --git a/helpers/hardhat-constants.ts b/helpers/hardhat-constants.ts index ef66a0b05..058e6d513 100644 --- a/helpers/hardhat-constants.ts +++ b/helpers/hardhat-constants.ts @@ -469,7 +469,6 @@ export const eContractidToContractName = { TimeLockProxy: "InitializableAdminUpgradeabilityProxy", TimeLockImpl: "TimeLock", DefaultTimeLockStrategy: "DefaultTimeLockStrategy", - NTokenOtherdeedImpl: "NTokenOtherdeed", NTokenChromieSquiggleImpl: "NTokenChromieSquiggle", NTokenStakefishImpl: "NTokenStakefish", HotWalletProxy: "HotWalletProxy", @@ -498,5 +497,4 @@ export const XTOKEN_TYPE_UPGRADE_WHITELIST = .split(/\s?,\s?/) .map((x) => +x); export const XTOKEN_SYMBOL_UPGRADE_WHITELIST = - process.env.XTOKEN_SYMBOL_UPGRADE_WHITELIST?.trim() - .split(/\s?,\s?/); + process.env.XTOKEN_SYMBOL_UPGRADE_WHITELIST?.trim().split(/\s?,\s?/); diff --git a/helpers/init-helpers.ts b/helpers/init-helpers.ts index a8cb6c807..a77fc556e 100644 --- a/helpers/init-helpers.ts +++ b/helpers/init-helpers.ts @@ -329,7 +329,6 @@ export const initReservesByHelper = async ( eContractid.NTokenBAKCImpl, eContractid.NTokenStakefishImpl, eContractid.NTokenChromieSquiggleImpl, - eContractid.NTokenOtherdeedImpl, ].includes(xTokenImpl) ) { xTokenType[symbol] = "nft"; diff --git a/helpers/types.ts b/helpers/types.ts index 5e6c1842e..b32ff0909 100644 --- a/helpers/types.ts +++ b/helpers/types.ts @@ -270,7 +270,6 @@ export enum eContractid { TimeLockProxy = "TimeLockProxy", TimeLockImpl = "TimeLockImpl", DefaultTimeLockStrategy = "DefaultTimeLockStrategy", - NTokenOtherdeedImpl = "NTokenOtherdeedImpl", NTokenChromieSquiggleImpl = "NTokenChromieSquiggleImpl", NTokenStakefishImpl = "NTokenStakefishImpl", HotWalletProxy = "HotWalletProxy", diff --git a/scripts/deployments/steps/11_allReserves.ts b/scripts/deployments/steps/11_allReserves.ts index a8043e0f9..7330fe550 100644 --- a/scripts/deployments/steps/11_allReserves.ts +++ b/scripts/deployments/steps/11_allReserves.ts @@ -97,7 +97,6 @@ export const step_11 = async (verify = false) => { xTokenImpl === eContractid.PYieldTokenImpl || xTokenImpl === eContractid.NTokenBAKCImpl || xTokenImpl === eContractid.NTokenStakefishImpl || - xTokenImpl === eContractid.NTokenOtherdeedImpl || xTokenImpl === eContractid.NTokenChromieSquiggleImpl ) as [string, IReserveParams][]; const chunkedReserves = chunk(reserves, 20); diff --git a/scripts/upgrade/ntoken.ts b/scripts/upgrade/ntoken.ts index ba3ca36f6..21713a50b 100644 --- a/scripts/upgrade/ntoken.ts +++ b/scripts/upgrade/ntoken.ts @@ -76,12 +76,14 @@ export const upgradeNToken = async (verify = false) => { continue; } - if (XTOKEN_SYMBOL_UPGRADE_WHITELIST && !XTOKEN_SYMBOL_UPGRADE_WHITELIST.includes(symbol)) { + if ( + XTOKEN_SYMBOL_UPGRADE_WHITELIST && + !XTOKEN_SYMBOL_UPGRADE_WHITELIST.includes(symbol) + ) { console.log(symbol + "not in XTOKEN_SYMBOL_UPGRADE_WHITELIST, skip..."); continue; } - if (xTokenType == XTokenType.NTokenBAYC) { if (!nTokenBAYCImplementationAddress) { console.log("deploy NTokenBAYC implementation");