From 6a703b472b1beb78f900a0ced5d07ebb10aa8f5a Mon Sep 17 00:00:00 2001 From: web3devLuBdF <289144272+web3devLuBdF@users.noreply.github.com> Date: Mon, 20 Jul 2026 16:54:09 +0200 Subject: [PATCH] added the hub connected with TreasurySpoke in the constructor, so each treasury spoke will be connected with a hub --- .../batches/AaveV4TreasurySpokeBatch.sol | 5 +++-- .../orchestration/AaveV4DeployBase.sol | 7 +++++-- .../orchestration/AaveV4DeployOrchestration.sol | 9 ++++++--- .../spoke/AaveV4TreasurySpokeDeployProcedure.sol | 16 +++++++++++++--- src/spoke/TreasurySpoke.sol | 7 +++++++ src/spoke/instances/TreasurySpokeInstance.sol | 2 +- tests/config-engine/BaseConfigEngine.t.sol | 2 ++ .../hub/configurator/HubConfigurator.t.sol | 6 ++++-- .../spoke/multi-hub/Spoke.MultipleHub.Base.t.sol | 1 + .../TreasurySpoke.Upgradeable.t.sol | 8 ++++---- .../contracts/treasury-spoke/TreasurySpoke.t.sol | 2 +- .../batches/AaveV4TreasurySpokeBatch.t.sol | 7 ++++--- tests/deployments/batches/BatchBase.t.sol | 1 + .../orchestration/AaveV4TestOrchestration.sol | 11 +++++++---- .../AaveV4TreasurySpokeDeployProcedure.t.sol | 5 +++-- .../helpers/mocks/MockTreasurySpokeInstance.sol | 2 +- ...AaveV4TreasurySpokeDeployProcedureWrapper.sol | 4 ++-- tests/setup/Base.t.sol | 1 + tests/utils/BatchTestProcedures.sol | 1 + 19 files changed, 67 insertions(+), 30 deletions(-) diff --git a/src/deployments/batches/AaveV4TreasurySpokeBatch.sol b/src/deployments/batches/AaveV4TreasurySpokeBatch.sol index 849caf882..ba0b5dead 100644 --- a/src/deployments/batches/AaveV4TreasurySpokeBatch.sol +++ b/src/deployments/batches/AaveV4TreasurySpokeBatch.sol @@ -13,8 +13,9 @@ contract AaveV4TreasurySpokeBatch is AaveV4TreasurySpokeDeployProcedure { /// @dev Constructor. /// @param owner_ The owner of the TreasurySpoke proxy admin and initializer. /// @param salt_ The CREATE2 salt for deterministic deployment. - constructor(address owner_, bytes32 salt_) { - address treasurySpoke = _deployTreasurySpoke({owner: owner_, salt: salt_}); + /// @param hub_ The hub connected with Treasury Spoke. + constructor(address owner_, bytes32 salt_, address hub_) { + address treasurySpoke = _deployTreasurySpoke({owner: owner_, salt: salt_, hub: hub_}); _report = BatchReports.TreasurySpokeBatchReport({treasurySpoke: treasurySpoke}); } diff --git a/src/deployments/orchestration/AaveV4DeployBase.sol b/src/deployments/orchestration/AaveV4DeployBase.sol index 5fef5ee8e..cff3ad9a0 100644 --- a/src/deployments/orchestration/AaveV4DeployBase.sol +++ b/src/deployments/orchestration/AaveV4DeployBase.sol @@ -49,14 +49,17 @@ library AaveV4DeployBase { /// @notice Deploys the Treasury Spoke batch containing the TreasurySpoke proxy. /// @param owner The owner of the TreasurySpoke. /// @param salt The CREATE2 salt for deterministic deployment. + /// @param hub The hub connected with Treasury Spoke. /// @return The Treasury Spoke batch report. function deployTreasurySpokeBatch( address owner, - bytes32 salt + bytes32 salt, + address hub ) internal returns (BatchReports.TreasurySpokeBatchReport memory) { AaveV4TreasurySpokeBatch treasurySpokeBatch = new AaveV4TreasurySpokeBatch({ owner_: owner, - salt_: salt + salt_: salt, + hub_: hub }); return treasurySpokeBatch.getReport(); } diff --git a/src/deployments/orchestration/AaveV4DeployOrchestration.sol b/src/deployments/orchestration/AaveV4DeployOrchestration.sol index 55d963730..3ff30db1a 100644 --- a/src/deployments/orchestration/AaveV4DeployOrchestration.sol +++ b/src/deployments/orchestration/AaveV4DeployOrchestration.sol @@ -36,6 +36,7 @@ library AaveV4DeployOrchestration { ) internal returns (OrchestrationReports.FullDeploymentReport memory report) { bytes32 salt = _deriveSalt({deployer: deployer, salt: deployInputs.salt}); report.salt = deployInputs.salt; + address hub; // Deploy Access Batch // initialize with deployer as access manager admin @@ -67,7 +68,8 @@ library AaveV4DeployOrchestration { report.treasurySpokeBatchReport = _deployTreasurySpokeBatch({ logger: logger, treasurySpokeOwner: deployInputs.treasurySpokeOwner, - salt: salt + salt: salt, + hub: hub }); // Validate label uniqueness (duplicate labels produce identical CREATE2 salts) @@ -331,10 +333,11 @@ library AaveV4DeployOrchestration { function _deployTreasurySpokeBatch( Logger logger, address treasurySpokeOwner, - bytes32 salt + bytes32 salt, + address hub ) internal returns (BatchReports.TreasurySpokeBatchReport memory report) { logger.logHeader1('deploying TreasurySpokeBatch'); - report = AaveV4DeployBase.deployTreasurySpokeBatch({owner: treasurySpokeOwner, salt: salt}); + report = AaveV4DeployBase.deployTreasurySpokeBatch({owner: treasurySpokeOwner, salt: salt, hub: hub}); logger.log('TreasurySpoke', report.treasurySpoke); logger.logNewLine(); return report; diff --git a/src/deployments/procedures/deploy/spoke/AaveV4TreasurySpokeDeployProcedure.sol b/src/deployments/procedures/deploy/spoke/AaveV4TreasurySpokeDeployProcedure.sol index aca87b2d0..5dab61996 100644 --- a/src/deployments/procedures/deploy/spoke/AaveV4TreasurySpokeDeployProcedure.sol +++ b/src/deployments/procedures/deploy/spoke/AaveV4TreasurySpokeDeployProcedure.sol @@ -12,12 +12,22 @@ contract AaveV4TreasurySpokeDeployProcedure is AaveV4DeployProcedureBase { /// @notice Deploys a Treasury Spoke instance via CREATE2 and sets up a transparent proxy. /// @param owner The owner of the proxy admin and the TreasurySpoke initializer. /// @param salt The CREATE2 salt for deterministic deployment. - /// @return The address of the deployed transparent proxy contract. - function _deployTreasurySpoke(address owner, bytes32 salt) internal returns (address) { + /// @param hub The hub connected with Treasury Spoke, encoded to prevent the CREATE2 deployment from failing. + /// @return The address of the deployed transparent proxy contract + function _deployTreasurySpoke(address owner, bytes32 salt, address hub) internal returns (address) { require(owner != address(0), 'invalid owner'); + + bytes memory bytecode = abi.encodePacked( + type(TreasurySpokeInstance).creationCode, + /* The hub must be encoded because it is an argument in the TreasurySpoke constructor, + constructor arguments can alter the final bytecode causing the + CREATE2 deterministic deployement to fail, so encoding ensures the deploy succeds */ + abi.encode(hub) + ); + address implementation = Create2Utils.create2Deploy( salt, - type(TreasurySpokeInstance).creationCode + bytecode ); return Create2Utils.proxify( diff --git a/src/spoke/TreasurySpoke.sol b/src/spoke/TreasurySpoke.sol index e617e573f..7d12af2e3 100644 --- a/src/spoke/TreasurySpoke.sol +++ b/src/spoke/TreasurySpoke.sol @@ -13,8 +13,15 @@ import {ITreasurySpoke} from 'src/spoke/interfaces/ITreasurySpoke.sol'; /// @dev Dedicated to a single user, controlled exclusively by the owner. /// @dev Allows withdraw to claim fees and supply to invest back into any Hub asset. abstract contract TreasurySpoke is ITreasurySpoke, Ownable2StepUpgradeable { + /// @notice The hub of treasury spoke, each treasury spoke must be connected with a hub + address public immutable HUB; + using SafeERC20 for IERC20; + constructor(address _hub) { + HUB = _hub; + } + /// @dev To be overridden by the inheriting TreasurySpoke instance contract. function initialize(address owner) external virtual; diff --git a/src/spoke/instances/TreasurySpokeInstance.sol b/src/spoke/instances/TreasurySpokeInstance.sol index 69ed7eb78..5c79c1adc 100644 --- a/src/spoke/instances/TreasurySpokeInstance.sol +++ b/src/spoke/instances/TreasurySpokeInstance.sol @@ -10,7 +10,7 @@ contract TreasurySpokeInstance is TreasurySpoke { uint64 public constant SPOKE_REVISION = 1; /// @dev Constructor. - constructor() { + constructor(address _hub) TreasurySpoke(_hub) { _disableInitializers(); } diff --git a/tests/config-engine/BaseConfigEngine.t.sol b/tests/config-engine/BaseConfigEngine.t.sol index 50c8e64c6..4c952285c 100644 --- a/tests/config-engine/BaseConfigEngine.t.sol +++ b/tests/config-engine/BaseConfigEngine.t.sol @@ -74,6 +74,7 @@ abstract contract BaseConfigEngineTest is Test, Create2TestHelper { address internal ACCOUNT = makeAddr('ACCOUNT'); address internal TARGET = makeAddr('TARGET'); address internal USER = makeAddr('USER'); + address internal TREASURY_SPOKE_HUB; AaveV4ConfigEngine internal engine; IAccessManager internal accessManager; @@ -129,6 +130,7 @@ abstract contract BaseConfigEngineTest is Test, Create2TestHelper { TestTypes.TestEnvReport memory report = AaveV4TestOrchestration.deployTestEnv({ admin: ADMIN, treasuryAdmin: ADMIN, + hub: TREASURY_SPOKE_HUB, hubCount: NUM_HUBS, spokeCount: NUM_SPOKES, nativeWrapper: address(weth), diff --git a/tests/contracts/hub/configurator/HubConfigurator.t.sol b/tests/contracts/hub/configurator/HubConfigurator.t.sol index a0fa67fbd..e100acc5f 100644 --- a/tests/contracts/hub/configurator/HubConfigurator.t.sol +++ b/tests/contracts/hub/configurator/HubConfigurator.t.sol @@ -332,7 +332,8 @@ contract HubConfiguratorTest is Base { // Change the fee receiver address newTreasurySpoke = AaveV4TestOrchestration.deployTestTreasurySpoke({ owner: HUB_ADMIN, - salt: bytes32('newTreasurySpoke1') + salt: bytes32('newTreasurySpoke1'), + hub: TreasurySpokeHub }); vm.prank(HUB_CONFIGURATOR_ADMIN); hubConfigurator.updateFeeReceiver(address(hub1), daiAssetId, newTreasurySpoke); @@ -403,7 +404,8 @@ contract HubConfiguratorTest is Base { // Change the fee receiver address newTreasurySpoke = AaveV4TestOrchestration.deployTestTreasurySpoke({ owner: HUB_ADMIN, - salt: bytes32('newTreasurySpoke2') + salt: bytes32('newTreasurySpoke2'), + hub: TreasurySpokeHub }); vm.prank(HUB_CONFIGURATOR_ADMIN); hubConfigurator.updateFeeReceiver(address(hub1), daiAssetId, newTreasurySpoke); diff --git a/tests/contracts/spoke/multi-hub/Spoke.MultipleHub.Base.t.sol b/tests/contracts/spoke/multi-hub/Spoke.MultipleHub.Base.t.sol index cffee4f13..bc65c5376 100644 --- a/tests/contracts/spoke/multi-hub/Spoke.MultipleHub.Base.t.sol +++ b/tests/contracts/spoke/multi-hub/Spoke.MultipleHub.Base.t.sol @@ -38,6 +38,7 @@ contract SpokeMultipleHubBase is Base { TestTypes.TestEnvReport memory report = AaveV4TestOrchestration.deployTestEnv({ admin: ADMIN, treasuryAdmin: ADMIN, + hub: TreasurySpokeHub, hubCount: 2, spokeCount: 2, nativeWrapper: makeAddr('nativeWrapper'), diff --git a/tests/contracts/treasury-spoke/TreasurySpoke.Upgradeable.t.sol b/tests/contracts/treasury-spoke/TreasurySpoke.Upgradeable.t.sol index 12dfc30e1..ddb977da4 100644 --- a/tests/contracts/treasury-spoke/TreasurySpoke.Upgradeable.t.sol +++ b/tests/contracts/treasury-spoke/TreasurySpoke.Upgradeable.t.sol @@ -127,7 +127,7 @@ contract TreasurySpokeUpgradeableTest is Base { } function test_proxy_constructor_revertsWith_InvalidAddress() public { - TreasurySpokeInstance impl = new TreasurySpokeInstance(); + TreasurySpokeInstance impl = new TreasurySpokeInstance(TreasurySpokeHub); vm.expectRevert( abi.encodeWithSelector(OwnableUpgradeable.OwnableInvalidOwner.selector, address(0)) ); @@ -139,7 +139,7 @@ contract TreasurySpokeUpgradeableTest is Base { } function test_proxy_reinitialization_revertsWith_CallerNotProxyAdmin() public { - TreasurySpokeInstance impl = new TreasurySpokeInstance(); + TreasurySpokeInstance impl = new TreasurySpokeInstance(TreasurySpokeHub); ITransparentUpgradeableProxy proxy = ITransparentUpgradeableProxy( AaveV4TestOrchestration.proxify( address(impl), @@ -148,7 +148,7 @@ contract TreasurySpokeUpgradeableTest is Base { ) ); - TreasurySpokeInstance impl2 = new TreasurySpokeInstance(); + TreasurySpokeInstance impl2 = new TreasurySpokeInstance(TreasurySpokeHub); vm.expectRevert(); vm.prank(_makeUser()); proxy.upgradeToAndCall( @@ -160,6 +160,6 @@ contract TreasurySpokeUpgradeableTest is Base { function _deployMockTreasurySpokeInstance( uint64 revision ) internal returns (MockTreasurySpokeInstance) { - return new MockTreasurySpokeInstance(revision); + return new MockTreasurySpokeInstance(revision, TreasurySpokeHub); } } diff --git a/tests/contracts/treasury-spoke/TreasurySpoke.t.sol b/tests/contracts/treasury-spoke/TreasurySpoke.t.sol index c97e108d8..50a413b90 100644 --- a/tests/contracts/treasury-spoke/TreasurySpoke.t.sol +++ b/tests/contracts/treasury-spoke/TreasurySpoke.t.sol @@ -48,7 +48,7 @@ contract TreasurySpokeTest is Base { } function test_deploy_reverts_on_invalid_params() public { - TreasurySpokeInstance impl = new TreasurySpokeInstance(); + TreasurySpokeInstance impl = new TreasurySpokeInstance(TreasurySpokeHub); vm.expectRevert( abi.encodeWithSelector(OwnableUpgradeable.OwnableInvalidOwner.selector, address(0)) ); diff --git a/tests/deployments/batches/AaveV4TreasurySpokeBatch.t.sol b/tests/deployments/batches/AaveV4TreasurySpokeBatch.t.sol index a45ef1d03..057c92951 100644 --- a/tests/deployments/batches/AaveV4TreasurySpokeBatch.t.sol +++ b/tests/deployments/batches/AaveV4TreasurySpokeBatch.t.sol @@ -9,7 +9,7 @@ contract AaveV4TreasurySpokeBatchTest is BatchBaseTest { function setUp() public override { super.setUp(); - treasurySpokeBatch = new AaveV4TreasurySpokeBatch({owner_: admin, salt_: salt}); + treasurySpokeBatch = new AaveV4TreasurySpokeBatch({owner_: admin, salt_: salt, hub_: TreasurySpokeHub}); report = treasurySpokeBatch.getReport(); } @@ -27,13 +27,14 @@ contract AaveV4TreasurySpokeBatchTest is BatchBaseTest { function test_revert_zeroOwner() public { vm.expectRevert('invalid owner'); - new AaveV4TreasurySpokeBatch({owner_: address(0), salt_: keccak256('zeroOwnerSalt')}); + new AaveV4TreasurySpokeBatch({owner_: address(0), salt_: keccak256('zeroOwnerSalt'), hub_: address(0)}); } function test_differentSaltProducesDifferentAddress() public { AaveV4TreasurySpokeBatch newBatch = new AaveV4TreasurySpokeBatch({ owner_: admin, - salt_: keccak256('differentSalt') + salt_: keccak256('differentSalt'), + hub_: TreasurySpokeHub }); assertNotEq(report.treasurySpoke, newBatch.getReport().treasurySpoke); } diff --git a/tests/deployments/batches/BatchBase.t.sol b/tests/deployments/batches/BatchBase.t.sol index 90eedb465..5f0237985 100644 --- a/tests/deployments/batches/BatchBase.t.sol +++ b/tests/deployments/batches/BatchBase.t.sol @@ -36,6 +36,7 @@ contract BatchBaseTest is Create2TestHelper { address public admin = makeAddr('admin'); address public feeReceiver = makeAddr('feeReceiver'); bytes32 public salt; + address public TreasurySpokeHub; address public accessManager; address public nativeWrapper; bytes internal hubBytecode; diff --git a/tests/deployments/orchestration/AaveV4TestOrchestration.sol b/tests/deployments/orchestration/AaveV4TestOrchestration.sol index 256fac747..73938c014 100644 --- a/tests/deployments/orchestration/AaveV4TestOrchestration.sol +++ b/tests/deployments/orchestration/AaveV4TestOrchestration.sol @@ -61,7 +61,8 @@ library AaveV4TestOrchestration { address nativeWrapper, bytes memory hubBytecode, bytes memory spokeBytecode, - bytes32 salt + bytes32 salt, + address hub ) external returns (TestTypes.TestEnvReport memory) { TestTypes.TestEnvReport memory report; @@ -77,7 +78,8 @@ library AaveV4TestOrchestration { report.treasurySpoke = AaveV4DeployBase .deployTreasurySpokeBatch({ owner: treasuryAdmin, - salt: keccak256(abi.encodePacked(salt, 'treasurySpoke')) + salt: keccak256(abi.encodePacked(salt, 'treasurySpoke')), + hub: hub }) .treasurySpoke; @@ -197,9 +199,10 @@ library AaveV4TestOrchestration { function deployTestTreasurySpoke( address owner, - bytes32 salt + bytes32 salt, + address hub ) external returns (address treasurySpoke) { - return AaveV4DeployBase.deployTreasurySpokeBatch({owner: owner, salt: salt}).treasurySpoke; + return AaveV4DeployBase.deployTreasurySpokeBatch({owner: owner, salt: salt, hub: hub}).treasurySpoke; } function configureHubsSpokes(ConfigData.AddSpokeParams[] memory paramsList) external { diff --git a/tests/deployments/procedures/deploy/spoke/AaveV4TreasurySpokeDeployProcedure.t.sol b/tests/deployments/procedures/deploy/spoke/AaveV4TreasurySpokeDeployProcedure.t.sol index 1d3e40751..dec192237 100644 --- a/tests/deployments/procedures/deploy/spoke/AaveV4TreasurySpokeDeployProcedure.t.sol +++ b/tests/deployments/procedures/deploy/spoke/AaveV4TreasurySpokeDeployProcedure.t.sol @@ -13,7 +13,8 @@ contract AaveV4TreasurySpokeDeployProcedureTest is ProceduresBase { function test_deployTreasurySpoke() public { address treasurySpoke = aaveV4TreasurySpokeDeployProcedureWrapper.deployTreasurySpoke( owner, - salt + salt, + hub ); assertEq(Ownable(treasurySpoke).owner(), owner); assertEq(Ownable(ProxyHelper.getProxyAdmin(treasurySpoke)).owner(), owner); @@ -21,6 +22,6 @@ contract AaveV4TreasurySpokeDeployProcedureTest is ProceduresBase { function test_deployTreasurySpoke_reverts() public { vm.expectRevert('invalid owner'); - aaveV4TreasurySpokeDeployProcedureWrapper.deployTreasurySpoke({owner: address(0), salt: salt}); + aaveV4TreasurySpokeDeployProcedureWrapper.deployTreasurySpoke({owner: address(0), salt: salt, hub: hub}); } } diff --git a/tests/helpers/mocks/MockTreasurySpokeInstance.sol b/tests/helpers/mocks/MockTreasurySpokeInstance.sol index 6e4bd7a1b..0268f64a0 100644 --- a/tests/helpers/mocks/MockTreasurySpokeInstance.sol +++ b/tests/helpers/mocks/MockTreasurySpokeInstance.sol @@ -13,7 +13,7 @@ contract MockTreasurySpokeInstance is TreasurySpoke { * @dev It sets the spoke revision and disables the initializers. * @param spokeRevision_ The revision of the spoke contract. */ - constructor(uint64 spokeRevision_) { + constructor(uint64 spokeRevision_, address _hub) TreasurySpoke(_hub) { SPOKE_REVISION = spokeRevision_; _disableInitializers(); } diff --git a/tests/helpers/mocks/deployments/procedures/AaveV4TreasurySpokeDeployProcedureWrapper.sol b/tests/helpers/mocks/deployments/procedures/AaveV4TreasurySpokeDeployProcedureWrapper.sol index 6a02d6899..ee364b547 100644 --- a/tests/helpers/mocks/deployments/procedures/AaveV4TreasurySpokeDeployProcedureWrapper.sol +++ b/tests/helpers/mocks/deployments/procedures/AaveV4TreasurySpokeDeployProcedureWrapper.sol @@ -6,7 +6,7 @@ import {AaveV4TreasurySpokeDeployProcedure} from 'src/deployments/procedures/dep contract AaveV4TreasurySpokeDeployProcedureWrapper is AaveV4TreasurySpokeDeployProcedure { bool public IS_TEST = true; - function deployTreasurySpoke(address owner, bytes32 salt) external returns (address) { - return _deployTreasurySpoke(owner, salt); + function deployTreasurySpoke(address owner, bytes32 salt, address hub) external returns (address) { + return _deployTreasurySpoke(owner, salt, hub); } } diff --git a/tests/setup/Base.t.sol b/tests/setup/Base.t.sol index f6f511f8e..d110b7132 100644 --- a/tests/setup/Base.t.sol +++ b/tests/setup/Base.t.sol @@ -159,6 +159,7 @@ abstract contract Base is BaseHelpers, BatchTestProcedures { report = AaveV4TestOrchestration.deployTestEnv({ admin: ADMIN, treasuryAdmin: TREASURY_ADMIN, + hub: TreasurySpokeHub, hubCount: numHubs, spokeCount: numSpokes, nativeWrapper: address(tokenList.weth), diff --git a/tests/utils/BatchTestProcedures.sol b/tests/utils/BatchTestProcedures.sol index ed33f852e..a46e097d3 100644 --- a/tests/utils/BatchTestProcedures.sol +++ b/tests/utils/BatchTestProcedures.sol @@ -46,6 +46,7 @@ contract BatchTestProcedures is Test, Create2TestHelper, WETHDeployProcedure { bytes4[] internal _hubFeeMinterRoleSelectors; bytes4[] internal _hubConfiguratorRoleSelectors; address internal _deployer = makeAddr('deployer'); + address internal TreasurySpokeHub; // Skip native wrapper check when nativeWrapper address is not available (e.g. post-deployment JSON report) bool internal _skipNativeWrapperCheck;