From 5329a2c2efb930e3fac30e83c76900b04a5749dd Mon Sep 17 00:00:00 2001 From: EndymionJkb Date: Tue, 6 Dec 2022 23:50:38 -0500 Subject: [PATCH] Add protocol id disambiguator to AaveLinearPoolFactory (#2115) * feat: add protocolId to differentiate between different protocols using the same Aave Linear Pool factory * feat: add protocol id registration * docs: add comments * test: fix AaveLinearPool tests * docs: improve event comment * docs: replace slang with common word * refactor: add protocol ID registration event * docs: fix comment --- .../contracts/aave/AaveLinearPoolFactory.sol | 84 +++++++++++++++---- pkg/pool-linear/test/AaveLinearPool.test.ts | 7 +- .../test/AaveLinearPoolFactory.test.ts | 77 +++++++++++++++-- 3 files changed, 144 insertions(+), 24 deletions(-) diff --git a/pkg/pool-linear/contracts/aave/AaveLinearPoolFactory.sol b/pkg/pool-linear/contracts/aave/AaveLinearPoolFactory.sol index df120305f6..782900f78a 100644 --- a/pkg/pool-linear/contracts/aave/AaveLinearPoolFactory.sol +++ b/pkg/pool-linear/contracts/aave/AaveLinearPoolFactory.sol @@ -38,6 +38,12 @@ contract AaveLinearPoolFactory is ReentrancyGuard, FactoryWidePauseWindow { + // Associate a name with each registered protocol that uses this factory. + struct ProtocolIdData { + string name; + bool registered; + } + // Used for create2 deployments uint256 private _nextRebalancerSalt; @@ -46,6 +52,16 @@ contract AaveLinearPoolFactory is address private _lastCreatedPool; string private _poolVersion; + // Maintain a set of recognized protocolIds. + mapping(uint256 => ProtocolIdData) private _protocolIds; + + // This event allows off-chain tools to differentiate between different protocols that use this factory + // to deploy Aave Linear Pools. + event AaveLinearPoolCreated(address indexed pool, uint256 protocolId); + + // Record protocol ID registrations. + event AaveLinearPoolProtocolIdRegistered(uint256 indexed protocolId, string name); + constructor( IVault vault, IProtocolFeePercentagesProvider protocolFeeProvider, @@ -57,14 +73,31 @@ contract AaveLinearPoolFactory is _poolVersion = poolVersion; } + /** + * @dev Return the address of the most recently created pool. + */ function getLastCreatedPool() external view override returns (address) { return _lastCreatedPool; } + /** + * @dev Return the pool version deployed by this factory. + */ function getPoolVersion() public view override returns (string memory) { return _poolVersion; } + /** + * @dev Return the name associated with the given protocolId, if registered. + */ + function getProtocolName(uint256 protocolId) external view returns (string memory) { + ProtocolIdData memory protocolIdData = _protocolIds[protocolId]; + + require(protocolIdData.registered, "Protocol ID not registered"); + + return protocolIdData.name; + } + function _create(bytes memory constructorArgs) internal virtual override returns (address) { address pool = super._create(constructorArgs); _lastCreatedPool = pool; @@ -73,7 +106,7 @@ contract AaveLinearPoolFactory is } /** - * @dev Deploys a new `AaveLinearPool`. + * @dev Deploys a new `AaveLinearPool` with a given protocolId. */ function create( string memory name, @@ -82,7 +115,8 @@ contract AaveLinearPoolFactory is IERC20 wrappedToken, uint256 upperTarget, uint256 swapFeePercentage, - address owner + address owner, + uint256 protocolId ) external nonReentrant returns (AaveLinearPool) { // We are going to deploy both an AaveLinearPool and an AaveLinearPoolRebalancer set as its Asset Manager, but // this creates a circular dependency problem: the Pool must know the Asset Manager's address in order to call @@ -109,20 +143,19 @@ contract AaveLinearPoolFactory is (uint256 pauseWindowDuration, uint256 bufferPeriodDuration) = getPauseConfiguration(); - AaveLinearPool.ConstructorArgs memory args = AaveLinearPool.ConstructorArgs({ - vault: getVault(), - name: name, - symbol: symbol, - mainToken: mainToken, - wrappedToken: wrappedToken, - assetManager: expectedRebalancerAddress, - upperTarget: upperTarget, - swapFeePercentage: swapFeePercentage, - pauseWindowDuration: pauseWindowDuration, - bufferPeriodDuration: bufferPeriodDuration, - owner: owner, - version: getPoolVersion() - }); + AaveLinearPool.ConstructorArgs memory args; + args.vault = getVault(); + args.name = name; + args.symbol = symbol; + args.mainToken = mainToken; + args.wrappedToken = wrappedToken; + args.assetManager = expectedRebalancerAddress; + args.upperTarget = upperTarget; + args.swapFeePercentage = swapFeePercentage; + args.pauseWindowDuration = pauseWindowDuration; + args.bufferPeriodDuration = bufferPeriodDuration; + args.owner = owner; + args.version = getPoolVersion(); AaveLinearPool pool = AaveLinearPool(_create(abi.encode(args))); @@ -135,7 +168,26 @@ contract AaveLinearPoolFactory is address actualRebalancerAddress = Create2.deploy(0, rebalancerSalt, rebalancerCreationCode); require(expectedRebalancerAddress == actualRebalancerAddress, "Rebalancer deployment failed"); + // Identify the protocolId associated with this pool. We do not require that the protocolId be registered. + emit AaveLinearPoolCreated(address(pool), protocolId); + // We don't return the Rebalancer's address, but that can be queried in the Vault by calling `getPoolTokenInfo`. return pool; } + + /** + * @notice Register an id (and name) to differentiate between multiple protocols using this factory. + * @dev This is a permissioned function. Protocol ids cannot be deregistered. + */ + function registerProtocolId(uint256 protocolId, string memory name) external authenticate { + require(!_protocolIds[protocolId].registered, "Protocol ID already registered"); + + _registerProtocolId(protocolId, name); + } + + function _registerProtocolId(uint256 protocolId, string memory name) private { + _protocolIds[protocolId] = ProtocolIdData({ name: name, registered: true }); + + emit AaveLinearPoolProtocolIdRegistered(protocolId, name); + } } diff --git a/pkg/pool-linear/test/AaveLinearPool.test.ts b/pkg/pool-linear/test/AaveLinearPool.test.ts index 0b2de10eba..b9d99e20c8 100644 --- a/pkg/pool-linear/test/AaveLinearPool.test.ts +++ b/pkg/pool-linear/test/AaveLinearPool.test.ts @@ -31,6 +31,7 @@ describe('AaveLinearPool', function () { let trader: SignerWithAddress, lp: SignerWithAddress, owner: SignerWithAddress; const POOL_SWAP_FEE_PERCENTAGE = fp(0.01); + const AAVE_PROTOCOL_ID = 0; before('setup', async () => { [, lp, trader, owner] = await ethers.getSigners(); @@ -66,7 +67,8 @@ describe('AaveLinearPool', function () { wrappedToken.address, bn(0), POOL_SWAP_FEE_PERCENTAGE, - owner.address + owner.address, + AAVE_PROTOCOL_ID ); const receipt = await tx.wait(); @@ -87,7 +89,8 @@ describe('AaveLinearPool', function () { wrappedToken.address, bn(0), POOL_SWAP_FEE_PERCENTAGE, - owner.address + owner.address, + AAVE_PROTOCOL_ID ) ).to.be.revertedWith('TOKENS_MISMATCH'); }); diff --git a/pkg/pool-linear/test/AaveLinearPoolFactory.test.ts b/pkg/pool-linear/test/AaveLinearPoolFactory.test.ts index 9d8ef7c034..472d84a913 100644 --- a/pkg/pool-linear/test/AaveLinearPoolFactory.test.ts +++ b/pkg/pool-linear/test/AaveLinearPoolFactory.test.ts @@ -11,10 +11,12 @@ import { deploy, deployedAt } from '@balancer-labs/v2-helpers/src/contract'; import { MAX_UINT112 } from '@balancer-labs/v2-helpers/src/constants'; import { advanceTime, currentTimestamp, MONTH } from '@balancer-labs/v2-helpers/src/time'; import Token from '@balancer-labs/v2-helpers/src/models/tokens/Token'; +import { sharedBeforeEach } from '@balancer-labs/v2-common/sharedBeforeEach'; +import { actionId } from '@balancer-labs/v2-helpers/src/models/misc/actions'; describe('AaveLinearPoolFactory', function () { let vault: Vault, tokens: TokenList, factory: Contract; - let creationTime: BigNumber, owner: SignerWithAddress; + let creationTime: BigNumber, admin: SignerWithAddress, owner: SignerWithAddress; let factoryVersion: string, poolVersion: string; const NAME = 'Balancer Linear Pool Token'; @@ -24,12 +26,20 @@ describe('AaveLinearPoolFactory', function () { const BASE_PAUSE_WINDOW_DURATION = MONTH * 3; const BASE_BUFFER_PERIOD_DURATION = MONTH; + const AAVE_PROTOCOL_ID = 0; + const BEEFY_PROTOCOL_ID = 1; + const STURDY_PROTOCOL_ID = 2; + + const AAVE_PROTOCOL_NAME = 'AAVE'; + const BEEFY_PROTOCOL_NAME = 'Beefy'; + const STURDY_PROTOCOL_NAME = 'Sturdy'; + before('setup signers', async () => { - [, owner] = await ethers.getSigners(); + [, admin, owner] = await ethers.getSigners(); }); sharedBeforeEach('deploy factory & tokens', async () => { - vault = await Vault.create(); + vault = await Vault.create({ admin }); const queries = await deploy('v2-standalone-utils/BalancerQueries', { args: [vault.address] }); factoryVersion = JSON.stringify({ name: 'AaveLinearPoolFactory', @@ -58,17 +68,24 @@ describe('AaveLinearPoolFactory', function () { }); async function createPool(): Promise { - const receipt = await factory.create( + const tx = await factory.create( NAME, SYMBOL, tokens.DAI.address, tokens.CDAI.address, UPPER_TARGET, POOL_SWAP_FEE_PERCENTAGE, - owner.address + owner.address, + AAVE_PROTOCOL_ID ); - const event = expectEvent.inReceipt(await receipt.wait(), 'PoolCreated'); + const receipt = await tx.wait(); + const event = expectEvent.inReceipt(receipt, 'PoolCreated'); + expectEvent.inReceipt(receipt, 'AaveLinearPoolCreated', { + pool: event.args.pool, + protocolId: AAVE_PROTOCOL_ID, + }); + return deployedAt('AaveLinearPool', event.args.pool); } @@ -202,4 +219,52 @@ describe('AaveLinearPoolFactory', function () { expect(bufferPeriodEndTime).to.equal(now); }); }); + + describe('protocol id', () => { + it('should not allow adding protocols without permission', async () => { + await expect(factory.registerProtocolId(AAVE_PROTOCOL_ID, 'AAVE')).to.be.revertedWith('SENDER_NOT_ALLOWED'); + }); + + context('with no registered protocols', () => { + it('should revert when asking for an unregistered protocol name', async () => { + await expect(factory.getProtocolName(AAVE_PROTOCOL_ID)).to.be.revertedWith('Protocol ID not registered'); + }); + }); + + context('with registered protocols', () => { + sharedBeforeEach('grant permissions', async () => { + const action = await actionId(factory, 'registerProtocolId'); + await vault.authorizer.connect(admin).grantPermissions([action], admin.address, [factory.address]); + }); + + sharedBeforeEach('register some protocols', async () => { + await factory.connect(admin).registerProtocolId(AAVE_PROTOCOL_ID, AAVE_PROTOCOL_NAME); + await factory.connect(admin).registerProtocolId(BEEFY_PROTOCOL_ID, BEEFY_PROTOCOL_NAME); + await factory.connect(admin).registerProtocolId(STURDY_PROTOCOL_ID, STURDY_PROTOCOL_NAME); + }); + + it('protocol ID registration should emit an event', async () => { + const OTHER_PROTOCOL_ID = 57; + const OTHER_PROTOCOL_NAME = 'Protocol 57'; + + const tx = await factory.connect(admin).registerProtocolId(OTHER_PROTOCOL_ID, OTHER_PROTOCOL_NAME); + expectEvent.inReceipt(await tx.wait(), 'AaveLinearPoolProtocolIdRegistered', { + protocolId: OTHER_PROTOCOL_ID, + name: OTHER_PROTOCOL_NAME, + }); + }); + + it('should register protocols', async () => { + expect(await factory.getProtocolName(AAVE_PROTOCOL_ID)).to.equal(AAVE_PROTOCOL_NAME); + expect(await factory.getProtocolName(BEEFY_PROTOCOL_ID)).to.equal(BEEFY_PROTOCOL_NAME); + expect(await factory.getProtocolName(STURDY_PROTOCOL_ID)).to.equal(STURDY_PROTOCOL_NAME); + }); + + it('should fail when a protocol is already registered', async () => { + await expect( + factory.connect(admin).registerProtocolId(STURDY_PROTOCOL_ID, 'Random protocol') + ).to.be.revertedWith('Protocol ID already registered'); + }); + }); + }); });