From 5c05b9d4cf678afcf8ad65064509de6f04921739 Mon Sep 17 00:00:00 2001 From: Josh Date: Thu, 26 Sep 2024 13:23:59 -0400 Subject: [PATCH] remove stateful functions from tokenPoolFactory --- .../tokenAdminRegistry/TokenPoolFactory.t.sol | 766 +++++++++--------- .../TokenPoolFactory/FactoryBurnMintERC20.sol | 268 ++++++ .../TokenPoolFactory/TokenPoolFactory.sol | 68 +- 3 files changed, 699 insertions(+), 403 deletions(-) create mode 100644 contracts/src/v0.8/ccip/tokenAdminRegistry/TokenPoolFactory/FactoryBurnMintERC20.sol diff --git a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenPoolFactory.t.sol b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenPoolFactory.t.sol index 33515eecaa..2a6ba093e3 100644 --- a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenPoolFactory.t.sol +++ b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/TokenPoolFactory.t.sol @@ -1,382 +1,422 @@ -// pragma solidity ^0.8.24; +pragma solidity ^0.8.24; -// import {IOwner} from "../../interfaces/IOwner.sol"; -// import {ITokenAdminRegistry} from "../../interfaces/ITokenAdminRegistry.sol"; +import {IOwner} from "../../interfaces/IOwner.sol"; +import {ITokenAdminRegistry} from "../../interfaces/ITokenAdminRegistry.sol"; -// import {OwnerIsCreator} from "../../../shared/access/OwnerIsCreator.sol"; +import {OwnerIsCreator} from "../../../shared/access/OwnerIsCreator.sol"; -// import {RateLimiter} from "../../libraries/RateLimiter.sol"; -// import {BurnMintTokenPool} from "../../pools/BurnMintTokenPool.sol"; -// import {TokenPool} from "../../pools/TokenPool.sol"; -// import {FactoryBurnMintERC20} from "../../tokenAdminRegistry/FactoryBurnMintERC20.sol"; -// import {RegistryModuleOwnerCustom} from "../../tokenAdminRegistry/RegistryModuleOwnerCustom.sol"; -// import {TokenAdminRegistry} from "../../tokenAdminRegistry/TokenAdminRegistry.sol"; -// import {TokenPoolFactory} from "../../tokenAdminRegistry/TokenPoolFactory.sol"; -// import {TokenAdminRegistrySetup} from "./TokenAdminRegistry.t.sol"; +import {RateLimiter} from "../../libraries/RateLimiter.sol"; +import {BurnMintTokenPool} from "../../pools/BurnMintTokenPool.sol"; +import {TokenPool} from "../../pools/TokenPool.sol"; +import {FactoryBurnMintERC20} from "../../tokenAdminRegistry/TokenPoolFactory/FactoryBurnMintERC20.sol"; +import {RegistryModuleOwnerCustom} from "../../tokenAdminRegistry/RegistryModuleOwnerCustom.sol"; +import {TokenAdminRegistry} from "../../tokenAdminRegistry/TokenAdminRegistry.sol"; +import {TokenPoolFactory} from "../../tokenAdminRegistry/TokenPoolFactory/TokenPoolFactory.sol"; +import {TokenAdminRegistrySetup} from "./TokenAdminRegistry.t.sol"; -// import {Create2} from "../../../vendor/openzeppelin-solidity/v5.0.2/contracts/utils/Create2.sol"; +import {Create2} from "../../../vendor/openzeppelin-solidity/v5.0.2/contracts/utils/Create2.sol"; -// contract TokenPoolFactorySetup is TokenAdminRegistrySetup { -// using Create2 for bytes32; +import {console2 as console} from "forge-std/console2.sol"; -// TokenPoolFactory internal s_tokenPoolFactory; -// RegistryModuleOwnerCustom internal s_registryModuleOwnerCustom; +contract TokenPoolFactorySetup is TokenAdminRegistrySetup { + using Create2 for bytes32; -// bytes internal s_poolInitCode; -// bytes internal s_poolInitArgs; + TokenPoolFactory internal s_tokenPoolFactory; + RegistryModuleOwnerCustom internal s_registryModuleOwnerCustom; -// bytes32 internal constant FAKE_SALT = keccak256(abi.encode("FAKE_SALT")); + bytes internal s_poolInitCode; + bytes internal s_poolInitArgs; -// address internal s_rmnProxy = address(0x1234); + bytes32 internal constant FAKE_SALT = keccak256(abi.encode("FAKE_SALT")); -// bytes internal s_tokenCreationParams; -// bytes internal s_tokenInitCode; + address internal s_rmnProxy = address(0x1234); -// uint256 public constant PREMINT_AMOUNT = 1e20; // 100 tokens in 18 decimals + bytes internal s_tokenCreationParams; + bytes internal s_tokenInitCode; -// function setUp() public virtual override { -// TokenAdminRegistrySetup.setUp(); + uint256 public constant PREMINT_AMOUNT = 1e20; // 100 tokens in 18 decimals -// s_registryModuleOwnerCustom = new RegistryModuleOwnerCustom(address(s_tokenAdminRegistry)); -// s_tokenAdminRegistry.addRegistryModule(address(s_registryModuleOwnerCustom)); + function setUp() public virtual override { + TokenAdminRegistrySetup.setUp(); -// s_tokenPoolFactory = -// new TokenPoolFactory(s_tokenAdminRegistry, s_registryModuleOwnerCustom, s_rmnProxy, address(s_sourceRouter)); + s_registryModuleOwnerCustom = new RegistryModuleOwnerCustom(address(s_tokenAdminRegistry)); + s_tokenAdminRegistry.addRegistryModule(address(s_registryModuleOwnerCustom)); -// // Create Init Code for BurnMintERC20 TestToken with 18 decimals and supply cap of max uint256 value -// s_tokenCreationParams = abi.encode("TestToken", "TT", 18, type(uint256).max, PREMINT_AMOUNT, OWNER); + s_tokenPoolFactory = new TokenPoolFactory( + s_tokenAdminRegistry, + s_registryModuleOwnerCustom, + s_rmnProxy, + address(s_sourceRouter) + ); -// s_tokenInitCode = abi.encodePacked(type(FactoryBurnMintERC20).creationCode, s_tokenCreationParams); + // Create Init Code for BurnMintERC20 TestToken with 18 decimals and supply cap of max uint256 value + s_tokenCreationParams = abi.encode("TestToken", "TT", 18, type(uint256).max, PREMINT_AMOUNT, OWNER); -// s_poolInitCode = type(BurnMintTokenPool).creationCode; + s_tokenInitCode = abi.encodePacked(type(FactoryBurnMintERC20).creationCode, s_tokenCreationParams); -// // Create Init Args for BurnMintTokenPool with no allowlist minus the token address -// address[] memory allowlist = new address[](1); -// allowlist[0] = OWNER; -// s_poolInitArgs = abi.encode(allowlist, address(0x1234), s_sourceRouter); -// } -// } + s_poolInitCode = type(BurnMintTokenPool).creationCode; -// contract TokenPoolFactoryTests is TokenPoolFactorySetup { -// using Create2 for bytes32; - -// function test_TokenPoolFactory_Constructor_Revert() public { -// // Revert cause the tokenAdminRegistry is address(0) -// vm.expectRevert(TokenPoolFactory.InvalidZeroAddress.selector); -// new TokenPoolFactory(ITokenAdminRegistry(address(0)), RegistryModuleOwnerCustom(address(0)), address(0), address(0)); - -// new TokenPoolFactory( -// ITokenAdminRegistry(address(0xdeadbeef)), -// RegistryModuleOwnerCustom(address(0xdeadbeef)), -// address(0xdeadbeef), -// address(0xdeadbeef) -// ); -// } - -// function test_createTokenPool_WithNoExistingTokenOnRemoteChain_Success() public { -// vm.startPrank(OWNER); - -// bytes32 dynamicSalt = keccak256(abi.encodePacked(FAKE_SALT, OWNER)); - -// address predictedTokenAddress = -// Create2.computeAddress(dynamicSalt, keccak256(s_tokenInitCode), address(s_tokenPoolFactory)); - -// // Create the constructor params for the predicted pool -// bytes memory poolCreationParams = abi.encode(predictedTokenAddress, new address[](0), s_rmnProxy, s_sourceRouter); - -// // Predict the address of the pool before we make the tx by using the init code and the params -// bytes memory predictedPoolInitCode = abi.encodePacked(s_poolInitCode, poolCreationParams); - -// address predictedPoolAddress = -// dynamicSalt.computeAddress(keccak256(predictedPoolInitCode), address(s_tokenPoolFactory)); - -// (address tokenAddress, address poolAddress) = s_tokenPoolFactory.deployTokenAndTokenPool( -// new TokenPoolFactory.RemoteTokenPoolInfo[](0), s_tokenInitCode, s_poolInitCode, poolCreationParams, FAKE_SALT -// ); - -// assertNotEq(address(0), tokenAddress, "Token Address should not be 0"); -// assertNotEq(address(0), poolAddress, "Pool Address should not be 0"); - -// assertEq(predictedTokenAddress, tokenAddress, "Token Address should have been predicted"); -// assertEq(predictedPoolAddress, poolAddress, "Pool Address should have been predicted"); - -// s_tokenAdminRegistry.acceptAdminRole(tokenAddress); -// OwnerIsCreator(tokenAddress).acceptOwnership(); -// OwnerIsCreator(poolAddress).acceptOwnership(); - -// assertEq(poolAddress, s_tokenAdminRegistry.getPool(tokenAddress), "Token Pool should be set"); -// assertEq(IOwner(tokenAddress).owner(), OWNER, "Token should be owned by the owner"); -// assertEq(IOwner(poolAddress).owner(), OWNER, "Token should be owned by the owner"); -// } - -// function test_createTokenPool_WithNoExistingRemoteContracts_predict_Success() public { -// vm.startPrank(OWNER); -// bytes32 dynamicSalt = keccak256(abi.encodePacked(FAKE_SALT, OWNER)); - -// // We have to create a new factory, registry module, and token admin registry to simulate the other chain -// TokenAdminRegistry newTokenAdminRegistry = new TokenAdminRegistry(); -// RegistryModuleOwnerCustom newRegistryModule = new RegistryModuleOwnerCustom(address(newTokenAdminRegistry)); - -// // We want to deploy a new factory and Owner Module. -// TokenPoolFactory newTokenPoolFactory = -// new TokenPoolFactory(newTokenAdminRegistry, newRegistryModule, s_rmnProxy, address(s_destRouter)); - -// newTokenAdminRegistry.addRegistryModule(address(newRegistryModule)); - -// TokenPoolFactory.RemoteChainConfig memory remoteChainConfig = -// TokenPoolFactory.RemoteChainConfig(address(newTokenPoolFactory), address(s_destRouter), address(s_rmnProxy)); - -// { -// TokenPoolFactory.RemoteChainConfig[] memory remoteChainConfigs = new TokenPoolFactory.RemoteChainConfig[](1); -// remoteChainConfigs[0] = remoteChainConfig; - -// TokenPoolFactory.RemoteChainConfigUpdate[] memory remoteChainConfigUpdates = -// new TokenPoolFactory.RemoteChainConfigUpdate[](1); -// remoteChainConfigUpdates[0] = TokenPoolFactory.RemoteChainConfigUpdate(DEST_CHAIN_SELECTOR, remoteChainConfig); - -// // Add the new token Factory to the remote chain config and set it for the simulated destination chain -// s_tokenPoolFactory.updateRemoteChainConfig(remoteChainConfigUpdates); -// } - -// // Create an array of remote pools where nothing exists yet, but we want to predict the address for -// // the new pool and token on DEST_CHAIN_SELECTOR -// TokenPoolFactory.RemoteTokenPoolInfo[] memory remoteTokenPools = new TokenPoolFactory.RemoteTokenPoolInfo[](1); - -// // The only field that matters is DEST_CHAIN_SELECTOR because we dont want any existing token pool or token -// // on the remote chain -// remoteTokenPools[0] = TokenPoolFactory.RemoteTokenPoolInfo( -// DEST_CHAIN_SELECTOR, "", "", s_tokenInitCode, RateLimiter.Config(false, 0, 0) -// ); - -// // Predict the address of the token and pool on the DESTINATION chain -// address predictedTokenAddress = dynamicSalt.computeAddress(keccak256(s_tokenInitCode), address(newTokenPoolFactory)); - -// // Since the remote chain information was provided, we should be able to get the information from the newly -// // deployed token pool using the available getter functions -// (, address poolAddress) = -// s_tokenPoolFactory.deployTokenAndTokenPool(remoteTokenPools, s_tokenInitCode, s_poolInitCode, "", FAKE_SALT); - -// // Ensure that the remote Token was set to the one we predicted -// assertEq( -// abi.encode(predictedTokenAddress), -// TokenPool(poolAddress).getRemoteToken(DEST_CHAIN_SELECTOR), -// "Token Address should have been predicted" -// ); - -// { -// // Create the constructor params for the predicted pool -// // The predictedTokenAddress is NOT abi-encoded since the raw evm-address -// // is used in the constructor params -// bytes memory predictedPoolCreationParams = -// abi.encode(predictedTokenAddress, new address[](0), s_rmnProxy, address(s_destRouter)); - -// // Take the init code and concat the destination params to it, the initCode shouldn't change -// bytes memory predictedPoolInitCode = abi.encodePacked(s_poolInitCode, predictedPoolCreationParams); - -// // Predict the address of the pool on the DESTINATION chain -// address predictedPoolAddress = -// dynamicSalt.computeAddress(keccak256(predictedPoolInitCode), address(newTokenPoolFactory)); - -// // Assert that the address set for the remote pool is the same as the predicted address -// assertEq( -// abi.encode(predictedPoolAddress), -// TokenPool(poolAddress).getRemotePool(DEST_CHAIN_SELECTOR), -// "Pool Address should have been predicted" -// ); -// } - -// // On the new token pool factory, representing a destination chain, -// // deploy a new token and a new pool -// (address newTokenAddress, address newPoolAddress) = newTokenPoolFactory.deployTokenAndTokenPool( -// new TokenPoolFactory.RemoteTokenPoolInfo[](0), s_tokenInitCode, s_poolInitCode, "", FAKE_SALT -// ); - -// assertEq( -// TokenPool(poolAddress).getRemotePool(DEST_CHAIN_SELECTOR), -// abi.encode(newPoolAddress), -// "New Pool Address should have been deployed correctly" -// ); - -// assertEq( -// TokenPool(poolAddress).getRemoteToken(DEST_CHAIN_SELECTOR), -// abi.encode(newTokenAddress), -// "New Token Address should have been deployed correctly" -// ); -// } - -// function test_createTokenPool_ExistingRemoteToken_AndPredictPool_Success() public { -// vm.startPrank(OWNER); -// bytes32 dynamicSalt = keccak256(abi.encodePacked(FAKE_SALT, OWNER)); - -// FactoryBurnMintERC20 newRemoteToken = -// new FactoryBurnMintERC20("TestToken", "TT", 18, type(uint256).max, PREMINT_AMOUNT, OWNER); - -// // We have to create a new factory, registry module, and token admin registry to simulate the other chain -// TokenAdminRegistry newTokenAdminRegistry = new TokenAdminRegistry(); -// RegistryModuleOwnerCustom newRegistryModule = new RegistryModuleOwnerCustom(address(newTokenAdminRegistry)); - -// // We want to deploy a new factory and Owner Module. -// TokenPoolFactory newTokenPoolFactory = -// new TokenPoolFactory(newTokenAdminRegistry, newRegistryModule, s_rmnProxy, address(s_destRouter)); - -// newTokenAdminRegistry.addRegistryModule(address(newRegistryModule)); - -// { -// TokenPoolFactory.RemoteChainConfig memory remoteChainConfig = -// TokenPoolFactory.RemoteChainConfig(address(newTokenPoolFactory), address(s_destRouter), address(s_rmnProxy)); - -// TokenPoolFactory.RemoteChainConfig[] memory remoteChainConfigs = new TokenPoolFactory.RemoteChainConfig[](1); -// remoteChainConfigs[0] = remoteChainConfig; - -// TokenPoolFactory.RemoteChainConfigUpdate[] memory remoteChainConfigUpdates = -// new TokenPoolFactory.RemoteChainConfigUpdate[](1); -// remoteChainConfigUpdates[0] = TokenPoolFactory.RemoteChainConfigUpdate(DEST_CHAIN_SELECTOR, remoteChainConfig); - -// // Add the new token Factory to the remote chain config and set it for the simulated destination chain -// s_tokenPoolFactory.updateRemoteChainConfig(remoteChainConfigUpdates); -// } - -// // Create an array of remote pools where nothing exists yet, but we want to predict the address for -// // the new pool and token on DEST_CHAIN_SELECTOR -// TokenPoolFactory.RemoteTokenPoolInfo[] memory remoteTokenPools = new TokenPoolFactory.RemoteTokenPoolInfo[](1); - -// // The only field that matters is DEST_CHAIN_SELECTOR because we dont want any existing token pool or token -// // on the remote chain -// remoteTokenPools[0] = TokenPoolFactory.RemoteTokenPoolInfo( -// DEST_CHAIN_SELECTOR, "", abi.encode(address(newRemoteToken)), s_tokenInitCode, RateLimiter.Config(false, 0, 0) -// ); - -// // Since the remote chain information was provided, we should be able to get the information from the newly -// // deployed token pool using the available getter functions -// (address tokenAddress, address poolAddress) = -// s_tokenPoolFactory.deployTokenAndTokenPool(remoteTokenPools, s_tokenInitCode, s_poolInitCode, "", FAKE_SALT); - -// assertEq(address(TokenPool(poolAddress).getToken()), tokenAddress, "Token Address should have been set locally"); - -// // Ensure that the remote Token was set to the one we predicted -// assertEq( -// abi.encode(address(newRemoteToken)), -// TokenPool(poolAddress).getRemoteToken(DEST_CHAIN_SELECTOR), -// "Token Address should have been predicted" -// ); - -// // Create the constructor params for the predicted pool -// // The predictedTokenAddress is NOT abi-encoded since the raw evm-address -// // is used in the constructor params -// bytes memory predictedPoolCreationParams = -// abi.encode(address(newRemoteToken), new address[](0), s_rmnProxy, address(s_destRouter)); - -// // Take the init code and concat the destination params to it, the initCode shouldn't change -// bytes memory predictedPoolInitCode = abi.encodePacked(s_poolInitCode, predictedPoolCreationParams); - -// // Predict the address of the pool on the DESTINATION chain -// address predictedPoolAddress = -// dynamicSalt.computeAddress(keccak256(predictedPoolInitCode), address(newTokenPoolFactory)); - -// // Assert that the address set for the remote pool is the same as the predicted address -// assertEq( -// abi.encode(predictedPoolAddress), -// TokenPool(poolAddress).getRemotePool(DEST_CHAIN_SELECTOR), -// "Pool Address should have been predicted" -// ); - -// // On the new token pool factory, representing a destination chain, -// // deploy a new token and a new pool -// address newPoolAddress = newTokenPoolFactory.deployTokenPoolWithExistingToken( -// address(newRemoteToken), new TokenPoolFactory.RemoteTokenPoolInfo[](0), s_poolInitCode, "", FAKE_SALT -// ); - -// assertEq( -// abi.encode(newRemoteToken), -// TokenPool(poolAddress).getRemoteToken(DEST_CHAIN_SELECTOR), -// "Remote Token Address should have been set correctly" -// ); - -// assertEq( -// TokenPool(poolAddress).getRemotePool(DEST_CHAIN_SELECTOR), -// abi.encode(newPoolAddress), -// "New Pool Address should have been deployed correctly" -// ); -// } - -// function test_createTokenPool_WithRemoteTokenAndRemotePool_Success() public { -// vm.startPrank(OWNER); - -// bytes32 dynamicSalt = keccak256(abi.encodePacked(FAKE_SALT, OWNER)); - -// bytes memory RANDOM_TOKEN_ADDRESS = abi.encode(makeAddr("RANDOM_TOKEN")); -// bytes memory RANDOM_POOL_ADDRESS = abi.encode(makeAddr("RANDOM_POOL")); - -// address predictedTokenAddress = dynamicSalt.computeAddress(keccak256(s_tokenInitCode), address(s_tokenPoolFactory)); - -// // Create the constructor params for the predicted pool -// bytes memory poolCreationParams = abi.encode(predictedTokenAddress, new address[](0), s_rmnProxy, s_sourceRouter); - -// // Create an array of remote pools with some fake addresses -// TokenPoolFactory.RemoteTokenPoolInfo[] memory remoteTokenPools = new TokenPoolFactory.RemoteTokenPoolInfo[](1); - -// remoteTokenPools[0] = TokenPoolFactory.RemoteTokenPoolInfo( -// DEST_CHAIN_SELECTOR, RANDOM_POOL_ADDRESS, RANDOM_TOKEN_ADDRESS, "", RateLimiter.Config(false, 0, 0) -// ); - -// (address tokenAddress, address poolAddress) = s_tokenPoolFactory.deployTokenAndTokenPool( -// remoteTokenPools, s_tokenInitCode, s_poolInitCode, poolCreationParams, FAKE_SALT -// ); - -// assertNotEq(address(0), tokenAddress, "Token Address should not be 0"); -// assertNotEq(address(0), poolAddress, "Pool Address should not be 0"); - -// s_tokenAdminRegistry.acceptAdminRole(tokenAddress); -// OwnerIsCreator(tokenAddress).acceptOwnership(); -// OwnerIsCreator(poolAddress).acceptOwnership(); - -// assertEq( -// TokenPool(poolAddress).getRemoteToken(DEST_CHAIN_SELECTOR), -// RANDOM_TOKEN_ADDRESS, -// "Remote Token Address should have been set" -// ); - -// assertEq( -// TokenPool(poolAddress).getRemotePool(DEST_CHAIN_SELECTOR), -// RANDOM_POOL_ADDRESS, -// "Remote Pool Address should have been set" -// ); - -// assertEq(poolAddress, s_tokenAdminRegistry.getPool(tokenAddress), "Token Pool should be set"); - -// assertEq(IOwner(tokenAddress).owner(), OWNER, "Token should be owned by the owner"); - -// assertEq(IOwner(poolAddress).owner(), OWNER, "Token should be owned by the owner"); -// } - -// function test_updateRemoteChainConfig_Success() public { -// TokenPoolFactory.RemoteChainConfig[] memory remoteChainConfigs = new TokenPoolFactory.RemoteChainConfig[](1); - -// TokenPoolFactory.RemoteChainConfig memory remoteChainConfig = TokenPoolFactory.RemoteChainConfig({ -// remotePoolFactory: address(0x1234), -// remoteRouter: address(0x5678), -// remoteRMNProxy: address(0x9abc) -// }); - -// remoteChainConfigs[0] = remoteChainConfig; - -// TokenPoolFactory.RemoteChainConfigUpdate[] memory remoteChainConfigUpdates = -// new TokenPoolFactory.RemoteChainConfigUpdate[](1); -// remoteChainConfigUpdates[0] = TokenPoolFactory.RemoteChainConfigUpdate(DEST_CHAIN_SELECTOR, remoteChainConfig); - -// s_tokenPoolFactory.updateRemoteChainConfig(remoteChainConfigUpdates); - -// TokenPoolFactory.RemoteChainConfig memory updatedRemoteChainConfig = -// s_tokenPoolFactory.getRemoteChainConfig(DEST_CHAIN_SELECTOR); - -// assertEq( -// remoteChainConfig.remotePoolFactory, -// updatedRemoteChainConfig.remotePoolFactory, -// "Token Pool Factory should be set" -// ); - -// assertEq(remoteChainConfig.remoteRouter, updatedRemoteChainConfig.remoteRouter, "Router should be set"); - -// assertEq(remoteChainConfig.remoteRMNProxy, updatedRemoteChainConfig.remoteRMNProxy, "RMN Proxy should be set"); -// } -// } + // Create Init Args for BurnMintTokenPool with no allowlist minus the token address + address[] memory allowlist = new address[](1); + allowlist[0] = OWNER; + s_poolInitArgs = abi.encode(allowlist, address(0x1234), s_sourceRouter); + } +} + +contract TokenPoolFactoryTests is TokenPoolFactorySetup { + using Create2 for bytes32; + + function test_TokenPoolFactory_Constructor_Revert() public { + // Revert cause the tokenAdminRegistry is address(0) + vm.expectRevert(TokenPoolFactory.InvalidZeroAddress.selector); + new TokenPoolFactory( + ITokenAdminRegistry(address(0)), + RegistryModuleOwnerCustom(address(0)), + address(0), + address(0) + ); + + new TokenPoolFactory( + ITokenAdminRegistry(address(0xdeadbeef)), + RegistryModuleOwnerCustom(address(0xdeadbeef)), + address(0xdeadbeef), + address(0xdeadbeef) + ); + } + + function test_createTokenPool_WithNoExistingTokenOnRemoteChain_Success() public { + vm.startPrank(OWNER); + + bytes32 dynamicSalt = keccak256(abi.encodePacked(FAKE_SALT, OWNER)); + + address predictedTokenAddress = Create2.computeAddress( + dynamicSalt, + keccak256(s_tokenInitCode), + address(s_tokenPoolFactory) + ); + + // Create the constructor params for the predicted pool + bytes memory poolCreationParams = abi.encode(predictedTokenAddress, new address[](0), s_rmnProxy, s_sourceRouter); + + // Predict the address of the pool before we make the tx by using the init code and the params + bytes memory predictedPoolInitCode = abi.encodePacked(s_poolInitCode, poolCreationParams); + + address predictedPoolAddress = dynamicSalt.computeAddress( + keccak256(predictedPoolInitCode), + address(s_tokenPoolFactory) + ); + + (address tokenAddress, address poolAddress) = s_tokenPoolFactory.deployTokenAndTokenPool( + new TokenPoolFactory.RemoteTokenPoolInfo[](0), + s_tokenInitCode, + s_poolInitCode, + poolCreationParams, + FAKE_SALT + ); + + assertNotEq(address(0), tokenAddress, "Token Address should not be 0"); + assertNotEq(address(0), poolAddress, "Pool Address should not be 0"); + + assertEq(predictedTokenAddress, tokenAddress, "Token Address should have been predicted"); + assertEq(predictedPoolAddress, poolAddress, "Pool Address should have been predicted"); + + s_tokenAdminRegistry.acceptAdminRole(tokenAddress); + OwnerIsCreator(tokenAddress).acceptOwnership(); + OwnerIsCreator(poolAddress).acceptOwnership(); + + assertEq(poolAddress, s_tokenAdminRegistry.getPool(tokenAddress), "Token Pool should be set"); + assertEq(IOwner(tokenAddress).owner(), OWNER, "Token should be owned by the owner"); + assertEq(IOwner(poolAddress).owner(), OWNER, "Token should be owned by the owner"); + } + + function test_createTokenPool_WithNoExistingRemoteContracts_predict_Success() public { + vm.startPrank(OWNER); + bytes32 dynamicSalt = keccak256(abi.encodePacked(FAKE_SALT, OWNER)); + + // We have to create a new factory, registry module, and token admin registry to simulate the other chain + TokenAdminRegistry newTokenAdminRegistry = new TokenAdminRegistry(); + RegistryModuleOwnerCustom newRegistryModule = new RegistryModuleOwnerCustom(address(newTokenAdminRegistry)); + + // We want to deploy a new factory and Owner Module. + TokenPoolFactory newTokenPoolFactory = new TokenPoolFactory( + newTokenAdminRegistry, + newRegistryModule, + s_rmnProxy, + address(s_destRouter) + ); + + newTokenAdminRegistry.addRegistryModule(address(newRegistryModule)); + + TokenPoolFactory.RemoteChainConfig memory remoteChainConfig = TokenPoolFactory.RemoteChainConfig( + address(newTokenPoolFactory), + address(s_destRouter), + address(s_rmnProxy) + ); + + // Create an array of remote pools where nothing exists yet, but we want to predict the address for + // the new pool and token on DEST_CHAIN_SELECTOR + TokenPoolFactory.RemoteTokenPoolInfo[] memory remoteTokenPools = new TokenPoolFactory.RemoteTokenPoolInfo[](1); + + // The only field that matters is DEST_CHAIN_SELECTOR because we dont want any existing token pool or token + // on the remote chain + remoteTokenPools[0] = TokenPoolFactory.RemoteTokenPoolInfo( + DEST_CHAIN_SELECTOR, // remoteChainSelector + "", // remotePoolAddress + type(BurnMintTokenPool).creationCode, // remotePoolInitCode + remoteChainConfig, // remoteChainConfig + "", // remoteTokenAddress + s_tokenInitCode, // remoteTokenInitCode + RateLimiter.Config(false, 0, 0) + ); + + // Predict the address of the token and pool on the DESTINATION chain + address predictedTokenAddress = dynamicSalt.computeAddress( + keccak256(s_tokenInitCode), + address(newTokenPoolFactory) + ); + + // Since the remote chain information was provided, we should be able to get the information from the newly + // deployed token pool using the available getter functions + (, address poolAddress) = s_tokenPoolFactory.deployTokenAndTokenPool( + remoteTokenPools, + s_tokenInitCode, + s_poolInitCode, + "", + FAKE_SALT + ); + + // Ensure that the remote Token was set to the one we predicted + assertEq( + abi.encode(predictedTokenAddress), + TokenPool(poolAddress).getRemoteToken(DEST_CHAIN_SELECTOR), + "Token Address should have been predicted" + ); + + { + // Create the constructor params for the predicted pool + // The predictedTokenAddress is NOT abi-encoded since the raw evm-address + // is used in the constructor params + bytes memory predictedPoolCreationParams = abi.encode( + predictedTokenAddress, + new address[](0), + s_rmnProxy, + address(s_destRouter) + ); + + // Take the init code and concat the destination params to it, the initCode shouldn't change + bytes memory predictedPoolInitCode = abi.encodePacked(s_poolInitCode, predictedPoolCreationParams); + + // Predict the address of the pool on the DESTINATION chain + address predictedPoolAddress = dynamicSalt.computeAddress( + keccak256(predictedPoolInitCode), + address(newTokenPoolFactory) + ); + + // Assert that the address set for the remote pool is the same as the predicted address + assertEq( + abi.encode(predictedPoolAddress), + TokenPool(poolAddress).getRemotePool(DEST_CHAIN_SELECTOR), + "Pool Address should have been predicted" + ); + } + + // On the new token pool factory, representing a destination chain, + // deploy a new token and a new pool + (address newTokenAddress, address newPoolAddress) = newTokenPoolFactory.deployTokenAndTokenPool( + new TokenPoolFactory.RemoteTokenPoolInfo[](0), + s_tokenInitCode, + s_poolInitCode, + "", + FAKE_SALT + ); + + assertEq( + TokenPool(poolAddress).getRemotePool(DEST_CHAIN_SELECTOR), + abi.encode(newPoolAddress), + "New Pool Address should have been deployed correctly" + ); + + assertEq( + TokenPool(poolAddress).getRemoteToken(DEST_CHAIN_SELECTOR), + abi.encode(newTokenAddress), + "New Token Address should have been deployed correctly" + ); + } + + function test_createTokenPool_ExistingRemoteToken_AndPredictPool_Success() public { + vm.startPrank(OWNER); + bytes32 dynamicSalt = keccak256(abi.encodePacked(FAKE_SALT, OWNER)); + + FactoryBurnMintERC20 newRemoteToken = new FactoryBurnMintERC20( + "TestToken", + "TT", + 18, + type(uint256).max, + PREMINT_AMOUNT, + OWNER + ); + + // We have to create a new factory, registry module, and token admin registry to simulate the other chain + TokenAdminRegistry newTokenAdminRegistry = new TokenAdminRegistry(); + RegistryModuleOwnerCustom newRegistryModule = new RegistryModuleOwnerCustom(address(newTokenAdminRegistry)); + + // We want to deploy a new factory and Owner Module. + TokenPoolFactory newTokenPoolFactory = new TokenPoolFactory( + newTokenAdminRegistry, + newRegistryModule, + s_rmnProxy, + address(s_destRouter) + ); + + newTokenAdminRegistry.addRegistryModule(address(newRegistryModule)); + + TokenPoolFactory.RemoteChainConfig memory remoteChainConfig = TokenPoolFactory.RemoteChainConfig( + address(newTokenPoolFactory), + address(s_destRouter), + address(s_rmnProxy) + ); + + // Create an array of remote pools where nothing exists yet, but we want to predict the address for + // the new pool and token on DEST_CHAIN_SELECTOR + TokenPoolFactory.RemoteTokenPoolInfo[] memory remoteTokenPools = new TokenPoolFactory.RemoteTokenPoolInfo[](1); + + // The only field that matters is DEST_CHAIN_SELECTOR because we dont want any existing token pool or token + // on the remote chain + remoteTokenPools[0] = TokenPoolFactory.RemoteTokenPoolInfo( + DEST_CHAIN_SELECTOR, // remoteChainSelector + "", // remotePoolAddress + type(BurnMintTokenPool).creationCode, // remotePoolInitCode + remoteChainConfig, // remoteChainConfig + abi.encode(address(newRemoteToken)), // remoteTokenAddress + s_tokenInitCode, // remoteTokenInitCode + RateLimiter.Config(false, 0, 0) // rateLimiterConfig + ); + + // Since the remote chain information was provided, we should be able to get the information from the newly + // deployed token pool using the available getter functions + (address tokenAddress, address poolAddress) = s_tokenPoolFactory.deployTokenAndTokenPool( + remoteTokenPools, + s_tokenInitCode, + s_poolInitCode, + "", + FAKE_SALT + ); + + assertEq(address(TokenPool(poolAddress).getToken()), tokenAddress, "Token Address should have been set locally"); + + // Ensure that the remote Token was set to the one we predicted + assertEq( + abi.encode(address(newRemoteToken)), + TokenPool(poolAddress).getRemoteToken(DEST_CHAIN_SELECTOR), + "Token Address should have been predicted" + ); + + // Create the constructor params for the predicted pool + // The predictedTokenAddress is NOT abi-encoded since the raw evm-address + // is used in the constructor params + bytes memory predictedPoolCreationParams = abi.encode( + address(newRemoteToken), + new address[](0), + s_rmnProxy, + address(s_destRouter) + ); + + // Take the init code and concat the destination params to it, the initCode shouldn't change + bytes memory predictedPoolInitCode = abi.encodePacked(s_poolInitCode, predictedPoolCreationParams); + + // Predict the address of the pool on the DESTINATION chain + address predictedPoolAddress = dynamicSalt.computeAddress( + keccak256(predictedPoolInitCode), + address(newTokenPoolFactory) + ); + + // Assert that the address set for the remote pool is the same as the predicted address + assertEq( + abi.encode(predictedPoolAddress), + TokenPool(poolAddress).getRemotePool(DEST_CHAIN_SELECTOR), + "Pool Address should have been predicted" + ); + + // On the new token pool factory, representing a destination chain, + // deploy a new token and a new pool + address newPoolAddress = newTokenPoolFactory.deployTokenPoolWithExistingToken( + address(newRemoteToken), + new TokenPoolFactory.RemoteTokenPoolInfo[](0), + s_poolInitCode, + "", + FAKE_SALT + ); + + assertEq( + abi.encode(newRemoteToken), + TokenPool(poolAddress).getRemoteToken(DEST_CHAIN_SELECTOR), + "Remote Token Address should have been set correctly" + ); + + assertEq( + TokenPool(poolAddress).getRemotePool(DEST_CHAIN_SELECTOR), + abi.encode(newPoolAddress), + "New Pool Address should have been deployed correctly" + ); + } + + function test_createTokenPool_WithRemoteTokenAndRemotePool_Success() public { + vm.startPrank(OWNER); + + bytes32 dynamicSalt = keccak256(abi.encodePacked(FAKE_SALT, OWNER)); + + bytes memory RANDOM_TOKEN_ADDRESS = abi.encode(makeAddr("RANDOM_TOKEN")); + bytes memory RANDOM_POOL_ADDRESS = abi.encode(makeAddr("RANDOM_POOL")); + + address predictedTokenAddress = dynamicSalt.computeAddress(keccak256(s_tokenInitCode), address(s_tokenPoolFactory)); + + // Create the constructor params for the predicted pool + bytes memory poolCreationParams = abi.encode(predictedTokenAddress, new address[](0), s_rmnProxy, s_sourceRouter); + + // Create an array of remote pools with some fake addresses + TokenPoolFactory.RemoteTokenPoolInfo[] memory remoteTokenPools = new TokenPoolFactory.RemoteTokenPoolInfo[](1); + + remoteTokenPools[0] = TokenPoolFactory.RemoteTokenPoolInfo( + DEST_CHAIN_SELECTOR, // remoteChainSelector + RANDOM_POOL_ADDRESS, // remotePoolAddress + type(BurnMintTokenPool).creationCode, // remotePoolInitCode + TokenPoolFactory.RemoteChainConfig(address(0), address(0), address(0)), // remoteChainConfig + RANDOM_TOKEN_ADDRESS, // remoteTokenAddress + "", // remoteTokenInitCode + RateLimiter.Config(false, 0, 0) // rateLimiterConfig + ); + + (address tokenAddress, address poolAddress) = s_tokenPoolFactory.deployTokenAndTokenPool( + remoteTokenPools, + s_tokenInitCode, + s_poolInitCode, + poolCreationParams, + FAKE_SALT + ); + + assertNotEq(address(0), tokenAddress, "Token Address should not be 0"); + assertNotEq(address(0), poolAddress, "Pool Address should not be 0"); + + s_tokenAdminRegistry.acceptAdminRole(tokenAddress); + OwnerIsCreator(tokenAddress).acceptOwnership(); + OwnerIsCreator(poolAddress).acceptOwnership(); + + assertEq( + TokenPool(poolAddress).getRemoteToken(DEST_CHAIN_SELECTOR), + RANDOM_TOKEN_ADDRESS, + "Remote Token Address should have been set" + ); + + assertEq( + TokenPool(poolAddress).getRemotePool(DEST_CHAIN_SELECTOR), + RANDOM_POOL_ADDRESS, + "Remote Pool Address should have been set" + ); + + assertEq(poolAddress, s_tokenAdminRegistry.getPool(tokenAddress), "Token Pool should be set"); + + assertEq(IOwner(tokenAddress).owner(), OWNER, "Token should be owned by the owner"); + + assertEq(IOwner(poolAddress).owner(), OWNER, "Token should be owned by the owner"); + } +} diff --git a/contracts/src/v0.8/ccip/tokenAdminRegistry/TokenPoolFactory/FactoryBurnMintERC20.sol b/contracts/src/v0.8/ccip/tokenAdminRegistry/TokenPoolFactory/FactoryBurnMintERC20.sol new file mode 100644 index 0000000000..334cc5ed59 --- /dev/null +++ b/contracts/src/v0.8/ccip/tokenAdminRegistry/TokenPoolFactory/FactoryBurnMintERC20.sol @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IBurnMintERC20} from "../../../shared/token/ERC20/IBurnMintERC20.sol"; +import {IOwnable} from "../../../shared/interfaces/IOwnable.sol"; +import {IGetCCIPAdmin} from "../../../ccip/interfaces/IGetCCIPAdmin.sol"; + +import {OwnerIsCreator} from "../../../shared/access/OwnerIsCreator.sol"; + +import {ERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/ERC20.sol"; +import {ERC20Burnable} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/extensions/ERC20Burnable.sol"; +import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; +import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/introspection/IERC165.sol"; +import {EnumerableSet} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/structs/EnumerableSet.sol"; + +/// @notice A basic ERC20 compatible token contract with burn and minting roles. +/// @dev The total supply can be limited during deployment. +contract FactoryBurnMintERC20 is IBurnMintERC20, IGetCCIPAdmin, IERC165, ERC20Burnable, OwnerIsCreator { + using EnumerableSet for EnumerableSet.AddressSet; + + error SenderNotMinter(address sender); + error SenderNotBurner(address sender); + error MaxSupplyExceeded(uint256 supplyAfterMint); + + event MintAccessGranted(address indexed minter); + event BurnAccessGranted(address indexed burner); + event MintAccessRevoked(address indexed minter); + event BurnAccessRevoked(address indexed burner); + + event CCIPAdminTransferred(address indexed previousAdmin, address indexed newAdmin); + + /// @dev The number of decimals for the token + uint8 internal immutable i_decimals; + + /// @dev The maximum supply of the token, 0 if unlimited + uint256 internal immutable i_maxSupply; + + /// @dev the CCIPAdmin can be used to register with the CCIP token admin registry, but has no other special powers, + /// and can only be transferred by the owner. + address internal s_ccipAdmin; + + /// @dev the allowed minter addresses + EnumerableSet.AddressSet internal s_minters; + /// @dev the allowed burner addresses + EnumerableSet.AddressSet internal s_burners; + + constructor( + string memory name, + string memory symbol, + uint8 decimals_, + uint256 maxSupply_, + uint256 preMint_, + address newOwner_ + ) ERC20(name, symbol) { + i_decimals = decimals_; + i_maxSupply = maxSupply_; + + s_ccipAdmin = newOwner_; + + // Mint the initial supply to the new Owner, saving gas by not calling if the mint amount is zero + if (preMint_ != 0) _mint(newOwner_, preMint_); + + // Grant the deployer the minter and burner roles. This contract is expected to be deployed by a factory + // contract that will transfer ownership to the correct address after deployment, so granting minting and burning + // privileges here saves gas by not requiring two transactions. + grantMintRole(newOwner_); + grantBurnRole(newOwner_); + } + + function supportsInterface(bytes4 interfaceId) public pure virtual override returns (bool) { + return + interfaceId == type(IERC20).interfaceId || + interfaceId == type(IBurnMintERC20).interfaceId || + interfaceId == type(IERC165).interfaceId || + interfaceId == type(IOwnable).interfaceId; + } + + // ================================================================ + // | ERC20 | + // ================================================================ + + /// @dev Returns the number of decimals used in its user representation. + function decimals() public view virtual override returns (uint8) { + return i_decimals; + } + + /// @dev Returns the max supply of the token, 0 if unlimited. + function maxSupply() public view virtual returns (uint256) { + return i_maxSupply; + } + + /// @dev Uses OZ ERC20 _transfer to disallow sending to address(0). + /// @dev Disallows sending to address(this) + function _transfer(address from, address to, uint256 amount) internal virtual override validAddress(to) { + super._transfer(from, to, amount); + } + + /// @dev Uses OZ ERC20 _approve to disallow approving for address(0). + /// @dev Disallows approving for address(this) + function _approve(address owner, address spender, uint256 amount) internal virtual override validAddress(spender) { + super._approve(owner, spender, amount); + } + + /// @dev Exists to be backwards compatible with the older naming convention. + /// @param spender the account being approved to spend on the users' behalf. + /// @param subtractedValue the amount being removed from the approval. + /// @return success Bool to return if the approval was successfully decreased. + function decreaseApproval(address spender, uint256 subtractedValue) external returns (bool success) { + return decreaseAllowance(spender, subtractedValue); + } + + /// @dev Exists to be backwards compatible with the older naming convention. + /// @param spender the account being approved to spend on the users' behalf. + /// @param addedValue the amount being added to the approval. + function increaseApproval(address spender, uint256 addedValue) external { + increaseAllowance(spender, addedValue); + } + + /// @notice Check if recipient is valid (not this contract address). + /// @param recipient the account we transfer/approve to. + /// @dev Reverts with an empty revert to be compatible with the existing link token when + /// the recipient is this contract address. + modifier validAddress(address recipient) virtual { + // solhint-disable-next-line reason-string, gas-custom-errors + if (recipient == address(this)) revert(); + _; + } + + // ================================================================ + // | Burning & minting | + // ================================================================ + + /// @inheritdoc ERC20Burnable + /// @dev Uses OZ ERC20 _burn to disallow burning from address(0). + /// @dev Decreases the total supply. + function burn(uint256 amount) public override(IBurnMintERC20, ERC20Burnable) onlyBurner { + super.burn(amount); + } + + /// @inheritdoc IBurnMintERC20 + /// @dev Alias for BurnFrom for compatibility with the older naming convention. + /// @dev Uses burnFrom for all validation & logic. + + function burn(address account, uint256 amount) public virtual override { + burnFrom(account, amount); + } + + /// @inheritdoc ERC20Burnable + /// @dev Uses OZ ERC20 _burn to disallow burning from address(0). + /// @dev Decreases the total supply. + function burnFrom(address account, uint256 amount) public override(IBurnMintERC20, ERC20Burnable) onlyBurner { + super.burnFrom(account, amount); + } + + /// @inheritdoc IBurnMintERC20 + /// @dev Uses OZ ERC20 _mint to disallow minting to address(0). + /// @dev Disallows minting to address(this) + /// @dev Increases the total supply. + function mint(address account, uint256 amount) external override onlyMinter validAddress(account) { + if (i_maxSupply != 0 && totalSupply() + amount > i_maxSupply) revert MaxSupplyExceeded(totalSupply() + amount); + + _mint(account, amount); + } + + // ================================================================ + // | Roles | + // ================================================================ + + /// @notice grants both mint and burn roles to `burnAndMinter`. + /// @dev calls public functions so this function does not require + /// access controls. This is handled in the inner functions. + function grantMintAndBurnRoles(address burnAndMinter) external { + grantMintRole(burnAndMinter); + grantBurnRole(burnAndMinter); + } + + /// @notice Grants mint role to the given address. + /// @dev only the owner can call this function. + function grantMintRole(address minter) public onlyOwner { + if (s_minters.add(minter)) { + emit MintAccessGranted(minter); + } + } + + /// @notice Grants burn role to the given address. + /// @dev only the owner can call this function. + /// @param burner the address to grant the burner role to + function grantBurnRole(address burner) public onlyOwner { + if (s_burners.add(burner)) { + emit BurnAccessGranted(burner); + } + } + + /// @notice Revokes mint role for the given address. + /// @dev only the owner can call this function. + /// @param minter the address to revoke the mint role from. + function revokeMintRole(address minter) public onlyOwner { + if (s_minters.remove(minter)) { + emit MintAccessRevoked(minter); + } + } + + /// @notice Revokes burn role from the given address. + /// @dev only the owner can call this function + /// @param burner the address to revoke the burner role from + function revokeBurnRole(address burner) public onlyOwner { + if (s_burners.remove(burner)) { + emit BurnAccessRevoked(burner); + } + } + + /// @notice Returns all permissioned minters + function getMinters() public view returns (address[] memory) { + return s_minters.values(); + } + + /// @notice Returns all permissioned burners + function getBurners() public view returns (address[] memory) { + return s_burners.values(); + } + + /// @notice Returns the current CCIPAdmin + function getCCIPAdmin() public view returns (address) { + return s_ccipAdmin; + } + + /// @notice Transfers the CCIPAdmin role to a new address + /// @dev only the owner can call this function, NOT the current ccipAdmin, and 1-step ownership transfer is used. + /// @param newAdmin The address to transfer the CCIPAdmin role to. Setting to address(0) is a valid way to revoke + /// the role + function setCCIPAdmin(address newAdmin) public onlyOwner { + address currentAdmin = s_ccipAdmin; + + s_ccipAdmin = newAdmin; + + emit CCIPAdminTransferred(currentAdmin, newAdmin); + } + + // ================================================================ + // | Access | + // ================================================================ + + /// @notice Checks whether a given address is a minter for this token. + /// @return true if the address is allowed to mint. + function isMinter(address minter) public view returns (bool) { + return s_minters.contains(minter); + } + + /// @notice Checks whether a given address is a burner for this token. + /// @return true if the address is allowed to burn. + function isBurner(address burner) public view returns (bool) { + return s_burners.contains(burner); + } + + /// @notice Checks whether the msg.sender is a permissioned minter for this token + /// @dev Reverts with a SenderNotMinter if the check fails + modifier onlyMinter() { + if (!isMinter(msg.sender)) revert SenderNotMinter(msg.sender); + _; + } + + /// @notice Checks whether the msg.sender is a permissioned burner for this token + /// @dev Reverts with a SenderNotBurner if the check fails + modifier onlyBurner() { + if (!isBurner(msg.sender)) revert SenderNotBurner(msg.sender); + _; + } +} diff --git a/contracts/src/v0.8/ccip/tokenAdminRegistry/TokenPoolFactory/TokenPoolFactory.sol b/contracts/src/v0.8/ccip/tokenAdminRegistry/TokenPoolFactory/TokenPoolFactory.sol index f129140da3..baef42b20d 100644 --- a/contracts/src/v0.8/ccip/tokenAdminRegistry/TokenPoolFactory/TokenPoolFactory.sol +++ b/contracts/src/v0.8/ccip/tokenAdminRegistry/TokenPoolFactory/TokenPoolFactory.sol @@ -6,12 +6,13 @@ import {ITokenAdminRegistry} from "../../interfaces/ITokenAdminRegistry.sol"; import {OwnerIsCreator} from "../../../shared/access/OwnerIsCreator.sol"; import {RateLimiter} from "../../libraries/RateLimiter.sol"; -import {BurnMintTokenPool} from "../../pools/BurnMintTokenPool.sol"; import {TokenPool} from "../../pools/TokenPool.sol"; -import {RegistryModuleOwnerCustom} from "./../RegistryModuleOwnerCustom.sol"; +import {RegistryModuleOwnerCustom} from "../RegistryModuleOwnerCustom.sol"; import {Create2} from "../../../vendor/openzeppelin-solidity/v5.0.2/contracts/utils/Create2.sol"; +import {console2 as console} from "forge-std/console2.sol"; + /// @notice A contract for deploying new tokens and token pools, and configuring them with the token admin registry /// @dev At the end of the transaction, the ownership transfer process will begin, but the user must accept the /// ownership transfer in a separate transaction. @@ -22,6 +23,12 @@ contract TokenPoolFactory is OwnerIsCreator, ITypeAndVersion { error InvalidZeroAddress(); + enum PoolType { + BurnMint, + LockRelease + Custom + } + struct RemoteTokenPoolInfo { uint64 remoteChainSelector; // The CCIP specific selector for the remote chain // The address of the remote pool to either deploy or use as is. If @@ -29,6 +36,15 @@ contract TokenPoolFactory is OwnerIsCreator, ITypeAndVersion { bytes remotePoolAddress; // The address of the remote token to either deploy or use as is // If the empty parameter flag is provided, the address will be predicted + bytes remotePoolInitCode; + // The addresses of the remote RMNProxy and Router to use as immutable params and the factory to use in + // address calculations + + + bytes remotePoolInitArgs; + // RemoteChainConfig remoteChainConfig; + RemoteChainConfig remoteChainConfig; + // the address of the remote token bytes remoteTokenAddress; // The init code for the remote token if it needs to be deployed // and includes all the constructor params already appended @@ -58,7 +74,7 @@ contract TokenPoolFactory is OwnerIsCreator, ITypeAndVersion { address private immutable i_rmnProxy; address private immutable i_ccipRouter; - mapping(uint64 remoteChainSelector => RemoteChainConfig remoteConfig) private s_remoteChainConfigs; + // mapping(uint64 remoteChainSelector => RemoteChainConfig remoteConfig) private s_remoteChainConfigs; constructor( ITokenAdminRegistry tokenAdminRegistry, @@ -160,7 +176,7 @@ contract TokenPoolFactory is OwnerIsCreator, ITypeAndVersion { RemoteTokenPoolInfo memory remoteTokenPool; for (uint256 i = 0; i < remoteTokenPools.length; i++) { remoteTokenPool = remoteTokenPools[i]; - RemoteChainConfig memory remoteChainConfig = s_remoteChainConfigs[remoteTokenPool.remoteChainSelector]; + // RemoteChainConfig memory remoteChainConfig = s_remoteChainConfigs[remoteTokenPool.remoteChainSelector]; // If the user provides an empty byte string, indicated no token has already been deployed, // then the address of the token needs to be predicted. Otherwise the address provided will be used. @@ -168,7 +184,10 @@ contract TokenPoolFactory is OwnerIsCreator, ITypeAndVersion { // The user must provide the initCode for the remote token, so its address can be predicted correctly. It's // provided in the remoteTokenInitCode field for the remoteTokenPool remoteTokenPool.remoteTokenAddress = abi.encode( - salt.computeAddress(keccak256(remoteTokenPool.remoteTokenInitCode), remoteChainConfig.remotePoolFactory) + salt.computeAddress( + keccak256(remoteTokenPool.remoteTokenInitCode), + remoteTokenPool.remoteChainConfig.remotePoolFactory + ) ); } @@ -181,20 +200,20 @@ contract TokenPoolFactory is OwnerIsCreator, ITypeAndVersion { // Combine the initCode with the initArgs to create the full deployment code bytes32 remotePoolInitcode = keccak256( bytes.concat( - type(BurnMintTokenPool).creationCode, + remoteTokenPool.remotePoolInitCode, // Calculate the remote pool Args with an empty allowList, remote RMN, and Remote Router addresses. abi.encode( abi.decode(remoteTokenPool.remoteTokenAddress, (address)), new address[](0), - remoteChainConfig.remoteRMNProxy, - remoteChainConfig.remoteRouter + remoteTokenPool.remoteChainConfig.remoteRMNProxy, + remoteTokenPool.remoteChainConfig.remoteRouter ) ) ); // Abi encode the computed remote address so it can be used as bytes in the chain update remoteTokenPool.remotePoolAddress = abi.encode( - salt.computeAddress(remotePoolInitcode, remoteChainConfig.remotePoolFactory) + salt.computeAddress(remotePoolInitcode, remoteTokenPool.remoteChainConfig.remotePoolFactory) ); } @@ -240,35 +259,4 @@ contract TokenPoolFactory is OwnerIsCreator, ITypeAndVersion { // Begin the 2 admin transfer process which must be accepted in a separate tx. i_tokenAdminRegistry.transferAdminRole(token, msg.sender); } - - // ================================================================ - // | Remote Chain Configuration | - // ================================================================ - - /// @notice Updates the remote chain config for the given remote chain selector - /// @param remoteChainConfigs An array of remote chain configs to update - /// @dev The function may only be called by the contract owner. - function updateRemoteChainConfig(RemoteChainConfigUpdate[] calldata remoteChainConfigs) external onlyOwner { - for (uint256 i = 0; i < remoteChainConfigs.length; ++i) { - RemoteChainConfig memory remoteConfig = remoteChainConfigs[i].remoteChainConfig; - - // Zero address validation check - if ( - remoteChainConfigs[i].remoteChainSelector == 0 || - remoteConfig.remotePoolFactory == address(0) || - remoteConfig.remoteRouter == address(0) || - remoteConfig.remoteRMNProxy == address(0) - ) revert InvalidZeroAddress(); - - s_remoteChainConfigs[remoteChainConfigs[i].remoteChainSelector] = remoteChainConfigs[i].remoteChainConfig; - emit RemoteChainConfigUpdated(remoteChainConfigs[i].remoteChainSelector, remoteConfig); - } - } - - /// @notice Get the remote chain config for a given remote chain selector - /// @param remoteChainSelector The remote chain selector to get the config for - /// @return remoteChainConfig The remote chain config for the given remote chain selector - function getRemoteChainConfig(uint64 remoteChainSelector) public view returns (RemoteChainConfig memory) { - return s_remoteChainConfigs[remoteChainSelector]; - } }