From 3f42407793b70a876c11bc8d9d9466343925c0ff Mon Sep 17 00:00:00 2001 From: Juan Ignacio Ubeira Date: Wed, 23 Nov 2022 09:58:48 -0300 Subject: [PATCH] Composable stable pool versioning (#2034) * WIP: version for ComposableStablePool. * Fix stack too deep error and adjust tests. * ComposableStablePoolFactory test. * Remove unused var. * Add factory version / pool version. * Fix benchmarks. * Remove IVersionProvider. --- .../contracts/pool-utils/IPoolVersion.sol | 28 +++++++++++++++++ .../contracts/pool-utils/IVersion.sol | 25 ++++++++++++++++ .../contracts/ComposableStablePool.sol | 11 ++++++- .../contracts/ComposableStablePoolFactory.sol | 30 +++++++++++++++---- .../ComposableStablePoolProtocolFees.sol | 2 +- .../test/ComposableStablePoolFactory.test.ts | 27 ++++++++++++++++- pvt/benchmarks/misc.ts | 2 +- .../models/pools/stable/StablePoolDeployer.ts | 2 ++ pvt/helpers/src/models/pools/stable/types.ts | 2 ++ .../src/models/types/TypesConverter.ts | 3 ++ 10 files changed, 122 insertions(+), 10 deletions(-) create mode 100644 pkg/interfaces/contracts/pool-utils/IPoolVersion.sol create mode 100644 pkg/interfaces/contracts/pool-utils/IVersion.sol diff --git a/pkg/interfaces/contracts/pool-utils/IPoolVersion.sol b/pkg/interfaces/contracts/pool-utils/IPoolVersion.sol new file mode 100644 index 0000000000..56d3e4cf88 --- /dev/null +++ b/pkg/interfaces/contracts/pool-utils/IPoolVersion.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +pragma solidity >=0.7.0 <0.9.0; + +/** + * @notice Simple interface to retrieve the version of pools deployed by a pool factory. + */ +interface IPoolVersion { + /** + * @dev Returns a JSON representation of the deployed pool version containing name, version number and task ID. + * + * This is typically only useful in complex Pool deployment schemes, where multiple subsystems need to know about + * each other. Note that this value will only be updated at factory creation time. + */ + function getPoolVersion() external view returns (string memory); +} diff --git a/pkg/interfaces/contracts/pool-utils/IVersion.sol b/pkg/interfaces/contracts/pool-utils/IVersion.sol new file mode 100644 index 0000000000..9079808ee2 --- /dev/null +++ b/pkg/interfaces/contracts/pool-utils/IVersion.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +pragma solidity >=0.7.0 <0.9.0; + +/** + * @notice Simple interface to retrieve the version of a deployed contract. + */ +interface IVersion { + /** + * @dev Returns a JSON representation of the contract version containing name, version number and task ID. + */ + function version() external view returns (string memory); +} diff --git a/pkg/pool-stable/contracts/ComposableStablePool.sol b/pkg/pool-stable/contracts/ComposableStablePool.sol index dbb6a2aee3..814e61d245 100644 --- a/pkg/pool-stable/contracts/ComposableStablePool.sol +++ b/pkg/pool-stable/contracts/ComposableStablePool.sol @@ -19,6 +19,7 @@ import "@balancer-labs/v2-interfaces/contracts/pool-stable/StablePoolUserData.so import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/BalancerErrors.sol"; import "@balancer-labs/v2-interfaces/contracts/standalone-utils/IProtocolFeePercentagesProvider.sol"; import "@balancer-labs/v2-interfaces/contracts/pool-utils/IRateProvider.sol"; +import "@balancer-labs/v2-interfaces/contracts/pool-utils/IVersion.sol"; import "@balancer-labs/v2-solidity-utils/contracts/math/FixedPoint.sol"; import "@balancer-labs/v2-solidity-utils/contracts/math/Math.sol"; @@ -50,6 +51,7 @@ import "./StableMath.sol"; */ contract ComposableStablePool is IRateProvider, + IVersion, BaseGeneralPool, StablePoolAmplification, ComposableStablePoolRates, @@ -64,6 +66,8 @@ contract ComposableStablePool is // We are preminting half of that value (rounded up). uint256 private constant _PREMINTED_TOKEN_BALANCE = 2**(111); + string private _version; + // The constructor arguments are received in a struct to work around stack-too-deep issues struct NewPoolParams { IVault vault; @@ -79,6 +83,7 @@ contract ComposableStablePool is uint256 pauseWindowDuration; uint256 bufferPeriodDuration; address owner; + string version; } constructor(NewPoolParams memory params) @@ -99,7 +104,7 @@ contract ComposableStablePool is ComposableStablePoolRates(_extractRatesParams(params)) ProtocolFeeCache(params.protocolFeeProvider, ProtocolFeeCache.DELEGATE_PROTOCOL_SWAP_FEES_SENTINEL) { - // solhint-disable-previous-line no-empty-blocks + _version = params.version; } // Translate parameters to avoid stack-too-deep issues in the constructor @@ -130,6 +135,10 @@ contract ComposableStablePool is }); } + function version() external view override returns (string memory) { + return _version; + } + /** * @notice Return the minimum BPT balance, required to avoid minimum token balances. * @dev This amount is minted and immediately burned on pool initialization, so that the total supply diff --git a/pkg/pool-stable/contracts/ComposableStablePoolFactory.sol b/pkg/pool-stable/contracts/ComposableStablePoolFactory.sol index 9002e9f5ab..35f711dfc9 100644 --- a/pkg/pool-stable/contracts/ComposableStablePoolFactory.sol +++ b/pkg/pool-stable/contracts/ComposableStablePoolFactory.sol @@ -15,6 +15,8 @@ pragma solidity ^0.7.0; pragma experimental ABIEncoderV2; +import "@balancer-labs/v2-interfaces/contracts/pool-utils/IPoolVersion.sol"; +import "@balancer-labs/v2-interfaces/contracts/pool-utils/IVersion.sol"; import "@balancer-labs/v2-interfaces/contracts/vault/IVault.sol"; import "@balancer-labs/v2-pool-utils/contracts/factories/BasePoolFactory.sol"; @@ -22,11 +24,26 @@ import "@balancer-labs/v2-pool-utils/contracts/factories/FactoryWidePauseWindow. import "./ComposableStablePool.sol"; -contract ComposableStablePoolFactory is BasePoolFactory, FactoryWidePauseWindow { - constructor(IVault vault, IProtocolFeePercentagesProvider protocolFeeProvider) - BasePoolFactory(vault, protocolFeeProvider, type(ComposableStablePool).creationCode) - { - // solhint-disable-previous-line no-empty-blocks +contract ComposableStablePoolFactory is IVersion, IPoolVersion, BasePoolFactory, FactoryWidePauseWindow { + string private _version; + string private _poolVersion; + + constructor( + IVault vault, + IProtocolFeePercentagesProvider protocolFeeProvider, + string memory factoryVersion, + string memory poolVersion + ) BasePoolFactory(vault, protocolFeeProvider, type(ComposableStablePool).creationCode) { + _version = factoryVersion; + _poolVersion = poolVersion; + } + + function version() external view override returns (string memory) { + return _version; + } + + function getPoolVersion() public view override returns (string memory) { + return _poolVersion; } /** @@ -61,7 +78,8 @@ contract ComposableStablePoolFactory is BasePoolFactory, FactoryWidePauseWindow swapFeePercentage: swapFeePercentage, pauseWindowDuration: pauseWindowDuration, bufferPeriodDuration: bufferPeriodDuration, - owner: owner + owner: owner, + version: getPoolVersion() }) ) ) diff --git a/pkg/pool-stable/contracts/ComposableStablePoolProtocolFees.sol b/pkg/pool-stable/contracts/ComposableStablePoolProtocolFees.sol index a7bfa2d424..149ecd0b84 100644 --- a/pkg/pool-stable/contracts/ComposableStablePoolProtocolFees.sol +++ b/pkg/pool-stable/contracts/ComposableStablePoolProtocolFees.sol @@ -272,7 +272,7 @@ abstract contract ComposableStablePoolProtocolFees is // To convert to a percentage of pool ownership, multiply by the rate, // then normalize against the final invariant uint256 protocolOwnershipPercentage = Math.divDown( - Math.mul(invariantDeltaFromFees, getProtocolFeePercentageCache(ProtocolFeeType.SWAP)), + Math.mul(invariantDeltaFromFees, getProtocolFeePercentageCache(ProtocolFeeType.SWAP)), postJoinExitInvariant ); diff --git a/pkg/pool-stable/test/ComposableStablePoolFactory.test.ts b/pkg/pool-stable/test/ComposableStablePoolFactory.test.ts index 110fdc8e90..e7a4fbad8c 100644 --- a/pkg/pool-stable/test/ComposableStablePoolFactory.test.ts +++ b/pkg/pool-stable/test/ComposableStablePoolFactory.test.ts @@ -27,6 +27,7 @@ describe('ComposableStablePoolFactory', function () { let createTime: BigNumber; let protocolFeeExemptFlags: boolean[]; + let factoryVersion: string, poolVersion: string; before('setup signers', async () => { [, owner] = await ethers.getSigners(); @@ -34,7 +35,19 @@ describe('ComposableStablePoolFactory', function () { sharedBeforeEach('deploy factory & tokens', async () => { vault = await Vault.create(); - factory = await deploy('ComposableStablePoolFactory', { args: [vault.address, vault.getFeesProvider().address] }); + factoryVersion = JSON.stringify({ + name: 'ComposableStablePoolFactory', + version: '1', + deployment: 'test-deployment', + }); + poolVersion = JSON.stringify({ + name: 'ComposableStablePool', + version: '0', + deployment: 'test-deployment', + }); + factory = await deploy('ComposableStablePoolFactory', { + args: [vault.address, vault.getFeesProvider().address, factoryVersion, poolVersion], + }); createTime = await currentTimestamp(); tokens = await TokenList.create(['baDAI', 'baUSDC', 'baUSDT'], { sorted: true }); @@ -70,10 +83,22 @@ describe('ComposableStablePoolFactory', function () { pool = await createPool(); }); + it('sets the factory version', async () => { + expect(await factory.version()).to.equal(factoryVersion); + }); + it('sets the vault', async () => { expect(await pool.getVault()).to.equal(vault.address); }); + it('sets the pool version', async () => { + expect(await pool.version()).to.equal(poolVersion); + }); + + it('gets pool version from the factory', async () => { + expect(await factory.getPoolVersion()).to.equal(poolVersion); + }); + it('registers tokens in the vault', async () => { const poolId = await pool.getPoolId(); const poolTokens = await vault.getPoolTokens(poolId); diff --git a/pvt/benchmarks/misc.ts b/pvt/benchmarks/misc.ts index 5ac1b17b0a..462e7c0c4c 100644 --- a/pvt/benchmarks/misc.ts +++ b/pvt/benchmarks/misc.ts @@ -207,7 +207,7 @@ async function deployPoolFromFactory( }); factory = await deploy(`${fullName}Factory`, { args: [baseFactory.address] }); } else if (poolName == 'ComposableStablePool') { - factory = await deploy(`${fullName}Factory`, { args: [vault.address, vault.getFeesProvider().address] }); + factory = await deploy(`${fullName}Factory`, { args: [vault.address, vault.getFeesProvider().address, '', ''] }); } else { factory = await deploy(`${fullName}Factory`, { args: [vault.address, vault.getFeesProvider().address] }); } diff --git a/pvt/helpers/src/models/pools/stable/StablePoolDeployer.ts b/pvt/helpers/src/models/pools/stable/StablePoolDeployer.ts index b4a98def62..044f53326a 100644 --- a/pvt/helpers/src/models/pools/stable/StablePoolDeployer.ts +++ b/pvt/helpers/src/models/pools/stable/StablePoolDeployer.ts @@ -36,6 +36,7 @@ export default { bufferPeriodDuration, amplificationParameter, from, + version, } = params; const owner = TypesConverter.toAddress(params.owner); @@ -56,6 +57,7 @@ export default { pauseWindowDuration, bufferPeriodDuration, owner, + version: version, }, ], from, diff --git a/pvt/helpers/src/models/pools/stable/types.ts b/pvt/helpers/src/models/pools/stable/types.ts index 45793b1eb0..83d4cb1ad6 100644 --- a/pvt/helpers/src/models/pools/stable/types.ts +++ b/pvt/helpers/src/models/pools/stable/types.ts @@ -125,6 +125,7 @@ export type RawStablePoolDeployment = { from?: SignerWithAddress; vault?: Vault; mockedVault?: boolean; + version?: string; }; export type StablePoolDeployment = { @@ -134,6 +135,7 @@ export type StablePoolDeployment = { rateProviders: Account[]; tokenRateCacheDurations: BigNumberish[]; exemptFromYieldProtocolFeeFlags: boolean[]; + version: string; pauseWindowDuration?: BigNumberish; bufferPeriodDuration?: BigNumberish; owner?: SignerWithAddress; diff --git a/pvt/helpers/src/models/types/TypesConverter.ts b/pvt/helpers/src/models/types/TypesConverter.ts index 08676f4d5d..1bd679125a 100644 --- a/pvt/helpers/src/models/types/TypesConverter.ts +++ b/pvt/helpers/src/models/types/TypesConverter.ts @@ -137,6 +137,7 @@ export default { swapFeePercentage, pauseWindowDuration, bufferPeriodDuration, + version, } = params; if (!tokens) tokens = new TokenList(); @@ -147,6 +148,7 @@ export default { if (!pauseWindowDuration) pauseWindowDuration = 3 * MONTH; if (!bufferPeriodDuration) bufferPeriodDuration = MONTH; if (!exemptFromYieldProtocolFeeFlags) exemptFromYieldProtocolFeeFlags = Array(tokens.length).fill(false); + if (!version) version = 'test'; return { tokens, @@ -158,6 +160,7 @@ export default { pauseWindowDuration, bufferPeriodDuration, owner: params.owner, + version, }; },