From 3da9f3cac4b106ea43e7d053ea5c37ebca208791 Mon Sep 17 00:00:00 2001 From: Petrovska Date: Mon, 24 Jun 2024 19:28:11 +0900 Subject: [PATCH 01/24] feat: factory skeleton --- src/RoboSaverVirtualModuleFactory.sol | 116 ++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 src/RoboSaverVirtualModuleFactory.sol diff --git a/src/RoboSaverVirtualModuleFactory.sol b/src/RoboSaverVirtualModuleFactory.sol new file mode 100644 index 0000000..2748050 --- /dev/null +++ b/src/RoboSaverVirtualModuleFactory.sol @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {IKeeperRegistryMaster} from "./interfaces/chainlink/IKeeperRegistryMaster.sol"; +import {IKeeperRegistrar} from "./interfaces/chainlink/IKeeperRegistrar.sol"; + +import {RoboSaverVirtualModule} from "./RoboSaverVirtualModule.sol"; + +contract RoboSaverVirtualModuleFactory { + /*////////////////////////////////////////////////////////////////////////// + DATA TYPES + //////////////////////////////////////////////////////////////////////////*/ + struct VirtualModuleDetails { + address virtualModuleAddress; + uint256 upkeepId; + } + + /*////////////////////////////////////////////////////////////////////////// + CONSTANTS + //////////////////////////////////////////////////////////////////////////*/ + + IKeeperRegistryMaster constant CL_REGISTRY = IKeeperRegistryMaster(0x299c92a219F61a82E91d2062A262f7157F155AC1); + IKeeperRegistrar constant CL_REGISTRAR = IKeeperRegistrar(0x0F7E163446AAb41DB5375AbdeE2c3eCC56D9aA32); + + /*////////////////////////////////////////////////////////////////////////// + PUBLIC STORAGE + //////////////////////////////////////////////////////////////////////////*/ + + // card -> (module address, upkeep id) + mapping(address => VirtualModuleDetails) public virtualModules; + + /*////////////////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////////////////*/ + event RoboSaverVirtualModuleCreated(address virtualModule, address card, uint256 upkeepId, uint256 timestamp); + + /*////////////////////////////////////////////////////////////////////////// + ERRORS + //////////////////////////////////////////////////////////////////////////*/ + error UpkeepZero(); + + /*////////////////////////////////////////////////////////////////////////// + CONSTRUCTOR + //////////////////////////////////////////////////////////////////////////*/ + + constructor() {} + + /*////////////////////////////////////////////////////////////////////////// + EXTERNAL METHODS + //////////////////////////////////////////////////////////////////////////*/ + + /// @notice Creates a virtual module for a card + /// @param _delayModule The address of the delay module + /// @param _roleModule The address of the role module + /// @param _buffer The buffer for the virtual module (configurable) + /// @param _slippage The slippage for the virtual module (configurable) + function createVirtualModule(address _delayModule, address _roleModule, uint256 _buffer, uint16 _slippage) + external + { + // @todo sanity checks on the inputs! + + // uses `CARD` address has to helps pre-determining the address of the virtual module given the salt + address virtualModule = address( + new RoboSaverVirtualModule{salt: keccak256(abi.encodePacked(msg.sender))}( + _delayModule, _roleModule, _buffer, _slippage + ) + ); + + uint256 upkeepId = _registerRoboSaverVirtualModule(virtualModule); + + emit RoboSaverVirtualModuleCreated(virtualModule, msg.sender, upkeepId, block.timestamp); + } + + /*////////////////////////////////////////////////////////////////////////// + INTERNAL METHODS + //////////////////////////////////////////////////////////////////////////*/ + + /// @notice Registers the virtual module in the Chainlink Keeper Registry + /// @param _virtualModule The address of the virtual module to be registered + function _registerRoboSaverVirtualModule(address _virtualModule) internal returns (uint256 upkeepID_) { + IKeeperRegistrar.RegistrationParams memory registrationParams = IKeeperRegistrar.RegistrationParams({ + name: string.concat(RoboSaverVirtualModule(_virtualModule).name(), "-", _addressToString(msg.sender)), + encryptedEmail: "", + upkeepContract: _virtualModule, + gasLimit: 2_000_000, + adminAddress: address(this), // @note the factory is the admin + triggerType: 0, + checkData: "", + triggerConfig: "", + offchainConfig: "", + amount: 5e18 // @note dummy value for now + }); + + upkeepID_ = CL_REGISTRAR.registerUpkeep(registrationParams); + if (upkeepID_ == 0) revert UpkeepZero(); + + virtualModules[msg.sender] = VirtualModuleDetails({virtualModuleAddress: _virtualModule, upkeepId: upkeepID_}); + } + + /// @notice Converts an address to a string + function _addressToString(address _addr) internal pure returns (string memory) { + bytes32 value = bytes32(uint256(uint160(_addr))); + bytes memory alphabet = "0123456789abcdef"; + + bytes memory str = new bytes(42); + str[0] = "0"; + str[1] = "x"; + + for (uint256 i = 0; i < 20; i++) { + str[2 + i * 2] = alphabet[uint8(value[i + 12] >> 4)]; + str[3 + i * 2] = alphabet[uint8(value[i + 12] & 0x0f)]; + } + + return string(str); + } +} From 1d16f9963a3009b112f881b1cbf82bc9471e1a09 Mon Sep 17 00:00:00 2001 From: Petrovska Date: Mon, 24 Jun 2024 19:45:11 +0900 Subject: [PATCH 02/24] feat: introduce role of factory in the virtual module & constructor --- script/GnosisPayInfraDeployment.s.sol | 18 +++++++++++++----- src/RoboSaverVirtualModule.sol | 13 +++++++++++-- src/RoboSaverVirtualModuleFactory.sol | 16 ++++++++++------ test/BaseFixture.sol | 9 +++++++-- 4 files changed, 41 insertions(+), 15 deletions(-) diff --git a/script/GnosisPayInfraDeployment.s.sol b/script/GnosisPayInfraDeployment.s.sol index 16b38a2..5a6c58b 100644 --- a/script/GnosisPayInfraDeployment.s.sol +++ b/script/GnosisPayInfraDeployment.s.sol @@ -8,13 +8,15 @@ import {Delay} from "@delay-module/Delay.sol"; import {Roles} from "@roles-module/Roles.sol"; import {Bouncer} from "@gnosispay-kit/Bouncer.sol"; +import {RoboSaverVirtualModuleFactory} from "../src/RoboSaverVirtualModuleFactory.sol"; import {RoboSaverVirtualModule} from "../src/RoboSaverVirtualModule.sol"; /// @notice Deploys a setup mirroring GnosisPay infrastructure components and RoboSaverVirtualModule, in the following order: /// 1. {RolesModule} /// 2. {DelayModule} /// 3. {Bouncer} -/// 4. {RoboSaverVirtualModule} +/// 4. {RoboSaverVirtualModuleFactory} +/// 5. {RoboSaverVirtualModule} contract GnosisPayInfraDeployment is Script { // safe target address constant GNOSIS_SAFE = 0xa4A4a4879dCD3289312884e9eC74Ed37f9a92a55; @@ -43,7 +45,8 @@ contract GnosisPayInfraDeployment is Script { Bouncer bouncerContract; - // robosaver module + // robosaver module & factory + RoboSaverVirtualModuleFactory roboModuleFactory; RoboSaverVirtualModule roboModule; function run() public { @@ -61,10 +64,15 @@ contract GnosisPayInfraDeployment is Script { // 3. {Bouncer} bouncerContract = new Bouncer(GNOSIS_SAFE, address(rolesModule), SET_ALLOWANCE_SELECTOR); - // 4. {RoboSaverVirtualModule} - roboModule = new RoboSaverVirtualModule(address(delayModule), address(rolesModule), 50e18, 200); + // 4. {RoboSaverVirtualModuleFactory} + roboModuleFactory = new RoboSaverVirtualModuleFactory(); - // 5. {Allowance config} + // 5. {RoboSaverVirtualModule} + roboModule = new RoboSaverVirtualModule( + address(roboModuleFactory), address(delayModule), address(rolesModule), 50e18, 200 + ); + + // 6. {Allowance config} rolesModule.setAllowance( SET_ALLOWANCE_KEY, MIN_EURE_ALLOWANCE, diff --git a/src/RoboSaverVirtualModule.sol b/src/RoboSaverVirtualModule.sol index a1ef948..923f4b3 100644 --- a/src/RoboSaverVirtualModule.sol +++ b/src/RoboSaverVirtualModule.sol @@ -71,6 +71,7 @@ contract RoboSaverVirtualModule is /*////////////////////////////////////////////////////////////////////////// PUBLIC STORAGE //////////////////////////////////////////////////////////////////////////*/ + address factory; IDelayModifier public delayModule; IRolesModifier public rolesModule; @@ -149,6 +150,7 @@ contract RoboSaverVirtualModule is error NotKeeper(address agent); error NotAdmin(address agent); + error NorAdminNeitherFactory(address agent); error ZeroAddressValue(); error ZeroUintValue(); @@ -174,11 +176,18 @@ contract RoboSaverVirtualModule is _; } + /// @notice Enforce that the function is called by the admin or the factory only + modifier onlyAdminAndFactory() { + if (msg.sender != CARD || msg.sender != factory) revert NorAdminNeitherFactory(msg.sender); + _; + } + /*////////////////////////////////////////////////////////////////////////// CONSTRUCTOR //////////////////////////////////////////////////////////////////////////*/ - constructor(address _delayModule, address _rolesModule, uint256 _buffer, uint16 _slippage) { + constructor(address _factory, address _delayModule, address _rolesModule, uint256 _buffer, uint16 _slippage) { + factory = _factory; delayModule = IDelayModifier(_delayModule); rolesModule = IRolesModifier(_rolesModule); buffer = _buffer; @@ -223,7 +232,7 @@ contract RoboSaverVirtualModule is /// @notice Assigns a new keeper address /// @param _keeper The address of the new keeper - function setKeeper(address _keeper) external onlyAdmin { + function setKeeper(address _keeper) external onlyAdminAndFactory { if (_keeper == address(0)) revert ZeroAddressValue(); address oldKeeper = keeper; diff --git a/src/RoboSaverVirtualModuleFactory.sol b/src/RoboSaverVirtualModuleFactory.sol index 2748050..e77ffed 100644 --- a/src/RoboSaverVirtualModuleFactory.sol +++ b/src/RoboSaverVirtualModuleFactory.sol @@ -62,12 +62,16 @@ contract RoboSaverVirtualModuleFactory { // uses `CARD` address has to helps pre-determining the address of the virtual module given the salt address virtualModule = address( new RoboSaverVirtualModule{salt: keccak256(abi.encodePacked(msg.sender))}( - _delayModule, _roleModule, _buffer, _slippage + address(this), _delayModule, _roleModule, _buffer, _slippage ) ); uint256 upkeepId = _registerRoboSaverVirtualModule(virtualModule); + // extracts forwarder address & sets keeper + address keeper = CL_REGISTRY.getForwarder(upkeepId); + RoboSaverVirtualModule(virtualModule).setKeeper(keeper); + emit RoboSaverVirtualModuleCreated(virtualModule, msg.sender, upkeepId, block.timestamp); } @@ -77,12 +81,12 @@ contract RoboSaverVirtualModuleFactory { /// @notice Registers the virtual module in the Chainlink Keeper Registry /// @param _virtualModule The address of the virtual module to be registered - function _registerRoboSaverVirtualModule(address _virtualModule) internal returns (uint256 upkeepID_) { + function _registerRoboSaverVirtualModule(address _virtualModule) internal returns (uint256 upkeepId_) { IKeeperRegistrar.RegistrationParams memory registrationParams = IKeeperRegistrar.RegistrationParams({ name: string.concat(RoboSaverVirtualModule(_virtualModule).name(), "-", _addressToString(msg.sender)), encryptedEmail: "", upkeepContract: _virtualModule, - gasLimit: 2_000_000, + gasLimit: 2_000_000, // @todo optimise from the gas logs the right value. perhaps we are overshooting adminAddress: address(this), // @note the factory is the admin triggerType: 0, checkData: "", @@ -91,10 +95,10 @@ contract RoboSaverVirtualModuleFactory { amount: 5e18 // @note dummy value for now }); - upkeepID_ = CL_REGISTRAR.registerUpkeep(registrationParams); - if (upkeepID_ == 0) revert UpkeepZero(); + upkeepId_ = CL_REGISTRAR.registerUpkeep(registrationParams); + if (upkeepId_ == 0) revert UpkeepZero(); - virtualModules[msg.sender] = VirtualModuleDetails({virtualModuleAddress: _virtualModule, upkeepId: upkeepID_}); + virtualModules[msg.sender] = VirtualModuleDetails({virtualModuleAddress: _virtualModule, upkeepId: upkeepId_}); } /// @notice Converts an address to a string diff --git a/test/BaseFixture.sol b/test/BaseFixture.sol index e51d57d..3d26cf2 100644 --- a/test/BaseFixture.sol +++ b/test/BaseFixture.sol @@ -16,6 +16,7 @@ import "@balancer-v2/interfaces/contracts/pool-stable/StablePoolUserData.sol"; import {IKeeperRegistryMaster} from "../src/interfaces/chainlink/IKeeperRegistryMaster.sol"; import {IKeeperRegistrar} from "../src/interfaces/chainlink/IKeeperRegistrar.sol"; +import {RoboSaverVirtualModuleFactory} from "../src/RoboSaverVirtualModuleFactory.sol"; import {RoboSaverVirtualModule} from "../src/RoboSaverVirtualModule.sol"; import {ISafeProxyFactory} from "@gnosispay-kit/interfaces/ISafeProxyFactory.sol"; @@ -80,7 +81,8 @@ contract BaseFixture is Test { ISafe safe; - // robosaver module + // robosaver module & factory + RoboSaverVirtualModuleFactory roboModuleFactory; RoboSaverVirtualModule roboModule; // Keeper address (forwarder): https://docs.chain.link/chainlink-automation/guides/forwarder#securing-your-upkeep @@ -113,7 +115,10 @@ contract BaseFixture is Test { bouncerContract = new Bouncer(address(safe), address(rolesModule), SET_ALLOWANCE_SELECTOR); - roboModule = new RoboSaverVirtualModule(address(delayModule), address(rolesModule), EURE_BUFFER, SLIPPAGE); + roboModuleFactory = new RoboSaverVirtualModuleFactory(); + roboModule = new RoboSaverVirtualModule( + address(roboModuleFactory), address(delayModule), address(rolesModule), EURE_BUFFER, SLIPPAGE + ); // enable robo module in the delay & gnosis safe for tests flow vm.startPrank(address(safe)); From e0361ef5348cbef9095178fa05f5dab8cd433dce Mon Sep 17 00:00:00 2001 From: Petrovska Date: Mon, 24 Jun 2024 20:05:47 +0900 Subject: [PATCH 03/24] feat: adapt to submodule grabbing --- src/RoboSaverVirtualModuleFactory.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/RoboSaverVirtualModuleFactory.sol b/src/RoboSaverVirtualModuleFactory.sol index e77ffed..381b31b 100644 --- a/src/RoboSaverVirtualModuleFactory.sol +++ b/src/RoboSaverVirtualModuleFactory.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {IKeeperRegistryMaster} from "./interfaces/chainlink/IKeeperRegistryMaster.sol"; +import {IKeeperRegistryMaster} from "@chainlink/automation/interfaces/v2_1/IKeeperRegistryMaster.sol"; import {IKeeperRegistrar} from "./interfaces/chainlink/IKeeperRegistrar.sol"; import {RoboSaverVirtualModule} from "./RoboSaverVirtualModule.sol"; From 4067329cf0defa2b50147ad0573c8f47ed17a235 Mon Sep 17 00:00:00 2001 From: Petrovska Date: Mon, 24 Jun 2024 20:18:19 +0900 Subject: [PATCH 04/24] feat: add infinite approval for smooth ops for LINK in constructor --- src/RoboSaverVirtualModuleFactory.sol | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/RoboSaverVirtualModuleFactory.sol b/src/RoboSaverVirtualModuleFactory.sol index 381b31b..716647d 100644 --- a/src/RoboSaverVirtualModuleFactory.sol +++ b/src/RoboSaverVirtualModuleFactory.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; +import {IERC20} from "@chainlink/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; import {IKeeperRegistryMaster} from "@chainlink/automation/interfaces/v2_1/IKeeperRegistryMaster.sol"; import {IKeeperRegistrar} from "./interfaces/chainlink/IKeeperRegistrar.sol"; @@ -19,6 +20,7 @@ contract RoboSaverVirtualModuleFactory { CONSTANTS //////////////////////////////////////////////////////////////////////////*/ + IERC20 constant LINK = IERC20(0xE2e73A1c69ecF83F464EFCE6A5be353a37cA09b2); IKeeperRegistryMaster constant CL_REGISTRY = IKeeperRegistryMaster(0x299c92a219F61a82E91d2062A262f7157F155AC1); IKeeperRegistrar constant CL_REGISTRAR = IKeeperRegistrar(0x0F7E163446AAb41DB5375AbdeE2c3eCC56D9aA32); @@ -43,7 +45,9 @@ contract RoboSaverVirtualModuleFactory { CONSTRUCTOR //////////////////////////////////////////////////////////////////////////*/ - constructor() {} + constructor() { + LINK.approve(address(CL_REGISTRAR), type(uint256).max); + } /*////////////////////////////////////////////////////////////////////////// EXTERNAL METHODS From fe207de1674fe1c880d8dc14ae0c232092468884 Mon Sep 17 00:00:00 2001 From: Petrovska Date: Mon, 24 Jun 2024 20:43:25 +0900 Subject: [PATCH 05/24] test: adapt base fixture towards new flow that factory deploys new virtal mods --- src/RoboSaverVirtualModule.sol | 2 +- src/RoboSaverVirtualModuleFactory.sol | 2 +- test/BaseFixture.sol | 44 +++++++++++---------------- 3 files changed, 20 insertions(+), 28 deletions(-) diff --git a/src/RoboSaverVirtualModule.sol b/src/RoboSaverVirtualModule.sol index d42dd67..f273894 100644 --- a/src/RoboSaverVirtualModule.sol +++ b/src/RoboSaverVirtualModule.sol @@ -178,7 +178,7 @@ contract RoboSaverVirtualModule is /// @notice Enforce that the function is called by the admin or the factory only modifier onlyAdminAndFactory() { - if (msg.sender != CARD || msg.sender != factory) revert NorAdminNeitherFactory(msg.sender); + if (msg.sender != CARD && msg.sender != factory) revert NorAdminNeitherFactory(msg.sender); _; } diff --git a/src/RoboSaverVirtualModuleFactory.sol b/src/RoboSaverVirtualModuleFactory.sol index 716647d..3ce7f80 100644 --- a/src/RoboSaverVirtualModuleFactory.sol +++ b/src/RoboSaverVirtualModuleFactory.sol @@ -96,7 +96,7 @@ contract RoboSaverVirtualModuleFactory { checkData: "", triggerConfig: "", offchainConfig: "", - amount: 5e18 // @note dummy value for now + amount: 200e18 // @note dummy value for now }); upkeepId_ = CL_REGISTRAR.registerUpkeep(registrationParams); diff --git a/test/BaseFixture.sol b/test/BaseFixture.sol index c6d6ff0..1010743 100644 --- a/test/BaseFixture.sol +++ b/test/BaseFixture.sol @@ -116,42 +116,26 @@ contract BaseFixture is Test { bouncerContract = new Bouncer(address(safe), address(rolesModule), SET_ALLOWANCE_SELECTOR); roboModuleFactory = new RoboSaverVirtualModuleFactory(); - roboModule = new RoboSaverVirtualModule( - address(roboModuleFactory), address(delayModule), address(rolesModule), EURE_BUFFER, SLIPPAGE - ); + // fund the factory with LINK for task top up + deal(LINK, address(roboModuleFactory), LINK_FOR_TASK_TOP_UP); // enable robo module in the delay & gnosis safe for tests flow vm.startPrank(address(safe)); + // create from factory new robo virtual module + roboModuleFactory.createVirtualModule(address(delayModule), address(delayModule), EURE_BUFFER, SLIPPAGE); + (address roboModuleAddress, uint256 upkeepId) = roboModuleFactory.virtualModules(address(safe)); + + roboModule = RoboSaverVirtualModule(roboModuleAddress); + // deduct keeper from the registry and factory upkeep id rerieved from factory storage + keeper = CL_REGISTRY.getForwarder(upkeepId); + delayModule.enableModule(address(roboModule)); delayModule.enableModule(address(safe)); safe.enableModule(address(delayModule)); safe.enableModule(address(rolesModule)); - // registering the task in CL automation service - deal(LINK, address(safe), LINK_FOR_TASK_TOP_UP); - IERC20(LINK).approve(address(CL_REGISTRAR), LINK_FOR_TASK_TOP_UP); - - IKeeperRegistrar.RegistrationParams memory registrationParams = IKeeperRegistrar.RegistrationParams({ - name: string.concat(roboModule.name(), "-", _addressToString(address(safe))), - encryptedEmail: "", - upkeepContract: address(roboModule), - gasLimit: 2_000_000, - adminAddress: address(safe), - triggerType: 0, - checkData: "", - triggerConfig: "", - offchainConfig: "", - amount: LINK_FOR_TASK_TOP_UP - }); - - uint256 upkeepID = CL_REGISTRAR.registerUpkeep(registrationParams); - assertNotEq(upkeepID, 0); - - keeper = CL_REGISTRY.getForwarder(upkeepID); - roboModule.setKeeper(keeper); - vm.stopPrank(); vm.prank(SAFE_EOA_SIGNER); @@ -176,8 +160,14 @@ contract BaseFixture is Test { deal(BPT_STEUR_EURE, address(safe), EURE_TO_MINT); + _labelKeyContracts(); + } + + /// @dev Labels key contracts for tracing + function _labelKeyContracts() internal { vm.label(EURE, "EURE"); vm.label(WETH, "WETH"); + vm.label(LINK, "LINK"); vm.label(address(safe), "GNOSIS_SAFE"); vm.label(address(delayModule), "DELAY_MODULE"); vm.label(address(bouncerContract), "BOUNCER_CONTRACT"); @@ -185,6 +175,8 @@ contract BaseFixture is Test { vm.label(address(roboModule), "ROBO_MODULE"); vm.label(BPT_STEUR_EURE, "BPT_STEUR_EURE"); vm.label(address(roboModule.BALANCER_VAULT()), "BALANCER_VAULT"); + vm.label(address(CL_REGISTRY), "CL_REGISTRY"); + vm.label(address(CL_REGISTRAR), "CL_REGISTRAR"); } function _addressToString(address _addr) internal pure returns (string memory) { From 7d0fe14e6b01f5481c7871866f1660f62c2f07b6 Mon Sep 17 00:00:00 2001 From: Gosuto Inzasheru Date: Mon, 24 Jun 2024 14:19:17 +0200 Subject: [PATCH 06/24] ci: run `forge t` first to get exit code --- .github/workflows/foundry.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/foundry.yml b/.github/workflows/foundry.yml index bd95a13..0f5afbc 100644 --- a/.github/workflows/foundry.yml +++ b/.github/workflows/foundry.yml @@ -47,7 +47,7 @@ jobs: forge build - name: Run Forge tests run: | - forge coverage --report lcov -vvv + forge test && forge coverage --report lcov -vvv - name: Upload coverage report to Codecov uses: "codecov/codecov-action@v4" with: From c5963bf8a3f108c4d9cf8bf7ab63cbd58ac12f9b Mon Sep 17 00:00:00 2001 From: Petrovska Date: Wed, 26 Jun 2024 20:05:51 +0900 Subject: [PATCH 07/24] feat: make the factory storage variable public --- src/RoboSaverVirtualModule.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/RoboSaverVirtualModule.sol b/src/RoboSaverVirtualModule.sol index f273894..6ce32f6 100644 --- a/src/RoboSaverVirtualModule.sol +++ b/src/RoboSaverVirtualModule.sol @@ -71,7 +71,7 @@ contract RoboSaverVirtualModule is /*////////////////////////////////////////////////////////////////////////// PUBLIC STORAGE //////////////////////////////////////////////////////////////////////////*/ - address factory; + address public factory; IDelayModifier public delayModule; IRolesModifier public rolesModule; From 946cfaadd7ac154faf88edacb9aaaeac03e5f763 Mon Sep 17 00:00:00 2001 From: Petrovska Date: Wed, 26 Jun 2024 20:09:45 +0900 Subject: [PATCH 08/24] feat: make it actually immutable variable `FACTORY` --- src/RoboSaverVirtualModule.sol | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/RoboSaverVirtualModule.sol b/src/RoboSaverVirtualModule.sol index 6ce32f6..5820b12 100644 --- a/src/RoboSaverVirtualModule.sol +++ b/src/RoboSaverVirtualModule.sol @@ -68,11 +68,11 @@ contract RoboSaverVirtualModule is IComposableStablePool immutable BPT_STEUR_EURE; + address public immutable FACTORY; + /*////////////////////////////////////////////////////////////////////////// PUBLIC STORAGE //////////////////////////////////////////////////////////////////////////*/ - address public factory; - IDelayModifier public delayModule; IRolesModifier public rolesModule; @@ -178,7 +178,7 @@ contract RoboSaverVirtualModule is /// @notice Enforce that the function is called by the admin or the factory only modifier onlyAdminAndFactory() { - if (msg.sender != CARD && msg.sender != factory) revert NorAdminNeitherFactory(msg.sender); + if (msg.sender != CARD && msg.sender != FACTORY) revert NorAdminNeitherFactory(msg.sender); _; } @@ -187,7 +187,8 @@ contract RoboSaverVirtualModule is //////////////////////////////////////////////////////////////////////////*/ constructor(address _factory, address _delayModule, address _rolesModule, uint256 _buffer, uint16 _slippage) { - factory = _factory; + FACTORY = _factory; + delayModule = IDelayModifier(_delayModule); rolesModule = IRolesModifier(_rolesModule); buffer = _buffer; From 9b68b85fdb510e69f330bad465f6fc532887a2c3 Mon Sep 17 00:00:00 2001 From: Petrovska Date: Wed, 26 Jun 2024 20:11:45 +0900 Subject: [PATCH 09/24] fix: use proper naming for the modifier --- src/RoboSaverVirtualModule.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/RoboSaverVirtualModule.sol b/src/RoboSaverVirtualModule.sol index 5820b12..45af445 100644 --- a/src/RoboSaverVirtualModule.sol +++ b/src/RoboSaverVirtualModule.sol @@ -177,7 +177,7 @@ contract RoboSaverVirtualModule is } /// @notice Enforce that the function is called by the admin or the factory only - modifier onlyAdminAndFactory() { + modifier onlyAdminOrFactory() { if (msg.sender != CARD && msg.sender != FACTORY) revert NorAdminNeitherFactory(msg.sender); _; } @@ -233,7 +233,7 @@ contract RoboSaverVirtualModule is /// @notice Assigns a new keeper address /// @param _keeper The address of the new keeper - function setKeeper(address _keeper) external onlyAdminAndFactory { + function setKeeper(address _keeper) external onlyAdminOrFactory { if (_keeper == address(0)) revert ZeroAddressValue(); address oldKeeper = keeper; From 78b75d67ef8fe31be6a1efcfb4c2f0a7a5e5c3af Mon Sep 17 00:00:00 2001 From: petrovska-petro <84875062+petrovska-petro@users.noreply.github.com> Date: Wed, 26 Jun 2024 20:14:34 +0900 Subject: [PATCH 10/24] Update test/BaseFixture.sol Co-authored-by: gosuto.eth --- test/BaseFixture.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/BaseFixture.sol b/test/BaseFixture.sol index 1010743..3144689 100644 --- a/test/BaseFixture.sol +++ b/test/BaseFixture.sol @@ -123,7 +123,7 @@ contract BaseFixture is Test { vm.startPrank(address(safe)); // create from factory new robo virtual module - roboModuleFactory.createVirtualModule(address(delayModule), address(delayModule), EURE_BUFFER, SLIPPAGE); + roboModuleFactory.createVirtualModule(address(delayModule), address(rolesModule), EURE_BUFFER, SLIPPAGE); (address roboModuleAddress, uint256 upkeepId) = roboModuleFactory.virtualModules(address(safe)); roboModule = RoboSaverVirtualModule(roboModuleAddress); From 17bdb027f65e28edf7de09aaa936c41c612c1bef Mon Sep 17 00:00:00 2001 From: Petrovska Date: Wed, 26 Jun 2024 20:17:30 +0900 Subject: [PATCH 11/24] fix: consistency on `rolesModule` everywhere --- src/RoboSaverVirtualModuleFactory.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/RoboSaverVirtualModuleFactory.sol b/src/RoboSaverVirtualModuleFactory.sol index 3ce7f80..c67abd9 100644 --- a/src/RoboSaverVirtualModuleFactory.sol +++ b/src/RoboSaverVirtualModuleFactory.sol @@ -55,10 +55,10 @@ contract RoboSaverVirtualModuleFactory { /// @notice Creates a virtual module for a card /// @param _delayModule The address of the delay module - /// @param _roleModule The address of the role module + /// @param _rolesModule The address of the roles module /// @param _buffer The buffer for the virtual module (configurable) /// @param _slippage The slippage for the virtual module (configurable) - function createVirtualModule(address _delayModule, address _roleModule, uint256 _buffer, uint16 _slippage) + function createVirtualModule(address _delayModule, address _rolesModule, uint256 _buffer, uint16 _slippage) external { // @todo sanity checks on the inputs! @@ -66,7 +66,7 @@ contract RoboSaverVirtualModuleFactory { // uses `CARD` address has to helps pre-determining the address of the virtual module given the salt address virtualModule = address( new RoboSaverVirtualModule{salt: keccak256(abi.encodePacked(msg.sender))}( - address(this), _delayModule, _roleModule, _buffer, _slippage + address(this), _delayModule, _rolesModule, _buffer, _slippage ) ); From f2b16b8737a4d5bffb32ccba339fa367f226a671 Mon Sep 17 00:00:00 2001 From: Petrovska Date: Wed, 26 Jun 2024 20:19:57 +0900 Subject: [PATCH 12/24] fix: follow english order of terminology Neither-Nor --- src/RoboSaverVirtualModule.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/RoboSaverVirtualModule.sol b/src/RoboSaverVirtualModule.sol index 45af445..2e54b28 100644 --- a/src/RoboSaverVirtualModule.sol +++ b/src/RoboSaverVirtualModule.sol @@ -150,7 +150,7 @@ contract RoboSaverVirtualModule is error NotKeeper(address agent); error NotAdmin(address agent); - error NorAdminNeitherFactory(address agent); + error NeitherAdminNorFactory(address agent); error ZeroAddressValue(); error ZeroUintValue(); @@ -178,7 +178,7 @@ contract RoboSaverVirtualModule is /// @notice Enforce that the function is called by the admin or the factory only modifier onlyAdminOrFactory() { - if (msg.sender != CARD && msg.sender != FACTORY) revert NorAdminNeitherFactory(msg.sender); + if (msg.sender != CARD && msg.sender != FACTORY) revert NeitherAdminNorFactory(msg.sender); _; } From bf508c4421851354d7ea6cb831d45a42c6f7cc9b Mon Sep 17 00:00:00 2001 From: Petrovska Date: Wed, 26 Jun 2024 20:34:58 +0900 Subject: [PATCH 13/24] test: fix a revert error towards new expected error type --- test/unit/SettersTest.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/SettersTest.t.sol b/test/unit/SettersTest.t.sol index e5b8dc8..6d04efc 100644 --- a/test/unit/SettersTest.t.sol +++ b/test/unit/SettersTest.t.sol @@ -32,7 +32,7 @@ contract SettersTest is BaseFixture { roboModule.setBuffer(1000); vm.prank(randomCaller); - vm.expectRevert(abi.encodeWithSelector(RoboSaverVirtualModule.NotAdmin.selector, randomCaller)); + vm.expectRevert(abi.encodeWithSelector(RoboSaverVirtualModule.NeitherAdminNorFactory.selector, randomCaller)); roboModule.setKeeper(address(554)); vm.prank(randomCaller); From cc7ad57e53105f8e80f854f36e19f1de0323dd5f Mon Sep 17 00:00:00 2001 From: Gosuto Inzasheru Date: Thu, 27 Jun 2024 19:30:43 +0200 Subject: [PATCH 14/24] fix: not needed anymore to run twice --- .github/workflows/foundry.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/foundry.yml b/.github/workflows/foundry.yml index 0f5afbc..bd95a13 100644 --- a/.github/workflows/foundry.yml +++ b/.github/workflows/foundry.yml @@ -47,7 +47,7 @@ jobs: forge build - name: Run Forge tests run: | - forge test && forge coverage --report lcov -vvv + forge coverage --report lcov -vvv - name: Upload coverage report to Codecov uses: "codecov/codecov-action@v4" with: From 133073c23f9a9926f0e3f1afcf21f6a1720d16cc Mon Sep 17 00:00:00 2001 From: petrovska-petro <84875062+petrovska-petro@users.noreply.github.com> Date: Fri, 28 Jun 2024 13:38:16 +0900 Subject: [PATCH 15/24] Update src/RoboSaverVirtualModuleFactory.sol Co-authored-by: gosuto.eth --- src/RoboSaverVirtualModuleFactory.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/RoboSaverVirtualModuleFactory.sol b/src/RoboSaverVirtualModuleFactory.sol index c67abd9..0e67ab7 100644 --- a/src/RoboSaverVirtualModuleFactory.sol +++ b/src/RoboSaverVirtualModuleFactory.sol @@ -63,7 +63,7 @@ contract RoboSaverVirtualModuleFactory { { // @todo sanity checks on the inputs! - // uses `CARD` address has to helps pre-determining the address of the virtual module given the salt + // uses `msg.sender` as a salt to make the deployed address of the virtual module deterministic address virtualModule = address( new RoboSaverVirtualModule{salt: keccak256(abi.encodePacked(msg.sender))}( address(this), _delayModule, _rolesModule, _buffer, _slippage From 26f9514e8231ce1804cdd2f16e37def80923ed1f Mon Sep 17 00:00:00 2001 From: Petrovska Date: Fri, 28 Jun 2024 16:19:41 +0900 Subject: [PATCH 16/24] feat: reorg files with data types in its own directory --- src/RoboSaverVirtualModule.sol | 54 +++++++------------------- src/RoboSaverVirtualModuleFactory.sol | 15 +++---- src/types/DataTypes.sol | 39 +++++++++++++++++++ test/integration/AdjustPoolTest.t.sol | 7 ++-- test/integration/ClosePoolTest.t.sol | 13 +++---- test/integration/TopupBptTest.t.sol | 12 +++--- test/integration/TopupEUReTest.t.sol | 12 +++--- test/unit/CheckerTest.t.sol | 20 ++++------ test/unit/InformativeMethodsTest.t.sol | 2 - 9 files changed, 87 insertions(+), 87 deletions(-) create mode 100644 src/types/DataTypes.sol diff --git a/src/RoboSaverVirtualModule.sol b/src/RoboSaverVirtualModule.sol index 2e54b28..91b7030 100644 --- a/src/RoboSaverVirtualModule.sol +++ b/src/RoboSaverVirtualModule.sol @@ -13,39 +13,14 @@ import "@balancer-v2/interfaces/contracts/pool-stable/StablePoolUserData.sol"; import {KeeperCompatibleInterface} from "@chainlink/automation/interfaces/KeeperCompatibleInterface.sol"; +import {VirtualModule} from "./types/DataTypes.sol"; + /// @title RoboSaver: turn your Gnosis Pay card into an automated savings account! /// @author onchainification.xyz /// @notice Deposit and withdraw $EURe from your Gnosis Pay card to a liquidity pool contract RoboSaverVirtualModule is KeeperCompatibleInterface // 1 inherited component { - /*////////////////////////////////////////////////////////////////////////// - DATA TYPES - //////////////////////////////////////////////////////////////////////////*/ - - /// @notice Enum representing the different types of pool actions - /// @custom:value0 WITHDRAW Withdraw $EURe from the pool to the card - /// @custom:value1 DEPOSIT Deposit $EURe from the card into the pool - /// @custom:value2 CLOSE Close the pool position by withdrawing all to $EURe - /// @custom:value3 EXEC_QUEUE_POOL_ACTION Execute the queued pool action - enum PoolAction { - WITHDRAW, - DEPOSIT, - CLOSE, - EXEC_QUEUE_POOL_ACTION - } - - /// @notice Struct representing the data needed to execute a queued transaction - /// @dev Nonce allows us to determine if the transaction queued originated from this virtual module - /// @param nonce The nonce of the queued transaction - /// @param target The address of the target contract - /// @param payload The payload of the transaction to be executed on the target contract - struct QueuedTx { - uint256 nonce; - address target; - bytes payload; - } - /*////////////////////////////////////////////////////////////////////////// CONSTANTS //////////////////////////////////////////////////////////////////////////*/ @@ -81,7 +56,7 @@ contract RoboSaverVirtualModule is uint16 public slippage; /// @dev Keeps track of the transaction queued up by the virtual module and allows internally to call `executeNextTx` - QueuedTx public queuedTx; + VirtualModule.QueuedTx public queuedTx; /*////////////////////////////////////////////////////////////////////////// PRIVATE STORAGE @@ -285,7 +260,7 @@ contract RoboSaverVirtualModule is if (queuedTx.nonce != 0) { /// @notice check if the transaction is still in cooldown or ready to exec if (_isInCoolDown(queuedTx.nonce)) return (false, bytes("Internal transaction in cooldown status")); - return (true, abi.encode(PoolAction.EXEC_QUEUE_POOL_ACTION, 0)); + return (true, abi.encode(VirtualModule.PoolAction.EXEC_QUEUE_POOL_ACTION, 0)); } uint256 balance = EURE.balanceOf(CARD); @@ -300,14 +275,14 @@ contract RoboSaverVirtualModule is uint256 deficit = dailyAllowance - balance + buffer; uint256 withdrawableEure = bptBalance * BPT_STEUR_EURE.getRate() * (MAX_BPS - slippage) / 1e18 / MAX_BPS; if (withdrawableEure < deficit) { - return (true, abi.encode(PoolAction.CLOSE, withdrawableEure)); + return (true, abi.encode(VirtualModule.PoolAction.CLOSE, withdrawableEure)); } else { - return (true, abi.encode(PoolAction.WITHDRAW, deficit)); + return (true, abi.encode(VirtualModule.PoolAction.WITHDRAW, deficit)); } } else if (balance > dailyAllowance + buffer) { /// @notice there is a surplus; we need to deposit into the pool uint256 surplus = balance - (dailyAllowance + buffer); - return (true, abi.encode(PoolAction.DEPOSIT, surplus)); + return (true, abi.encode(VirtualModule.PoolAction.DEPOSIT, surplus)); } /// @notice neither deficit nor surplus; no action needed @@ -316,7 +291,8 @@ contract RoboSaverVirtualModule is function performUpkeep(bytes calldata _performData) external override onlyKeeper { // decode `_performData` - (PoolAction action, uint256 amount) = abi.decode(_performData, (PoolAction, uint256)); + (VirtualModule.PoolAction action, uint256 amount) = + abi.decode(_performData, (VirtualModule.PoolAction, uint256)); _adjustPool(action, amount); } @@ -327,18 +303,18 @@ contract RoboSaverVirtualModule is /// @notice Adjust the pool by depositing or withdrawing $EURe /// @param _action The action to take: deposit or withdraw /// @param _amount The amount of $EURe to deposit or withdraw - function _adjustPool(PoolAction _action, uint256 _amount) internal { + function _adjustPool(VirtualModule.PoolAction _action, uint256 _amount) internal { if (!delayModule.isModuleEnabled(address(this))) revert VirtualModuleNotEnabled(); if (_isCleanQueueRequired()) delayModule.skipExpired(); if (_isExternalTxQueued()) revert ExternalTxIsQueued(); - if (_action == PoolAction.WITHDRAW) { + if (_action == VirtualModule.PoolAction.WITHDRAW) { _poolWithdrawal(_amount); - } else if (_action == PoolAction.DEPOSIT) { + } else if (_action == VirtualModule.PoolAction.DEPOSIT) { _poolDeposit(_amount); - } else if (_action == PoolAction.CLOSE) { + } else if (_action == VirtualModule.PoolAction.CLOSE) { _poolClose(_amount); - } else if (_action == PoolAction.EXEC_QUEUE_POOL_ACTION) { + } else if (_action == VirtualModule.PoolAction.EXEC_QUEUE_POOL_ACTION) { _executeQueuedTx(); } } @@ -459,7 +435,7 @@ contract RoboSaverVirtualModule is : IDelayModifier.DelayModuleOperation.Call; delayModule.execTransactionFromModule(_target, 0, _payload, operation); uint256 cachedQueueNonce = delayModule.queueNonce(); - queuedTx = QueuedTx(cachedQueueNonce, _target, _payload); + queuedTx = VirtualModule.QueuedTx(cachedQueueNonce, _target, _payload); emit AdjustPoolTxDataQueued(_target, _payload, cachedQueueNonce); } diff --git a/src/RoboSaverVirtualModuleFactory.sol b/src/RoboSaverVirtualModuleFactory.sol index 0e67ab7..5f19aaf 100644 --- a/src/RoboSaverVirtualModuleFactory.sol +++ b/src/RoboSaverVirtualModuleFactory.sol @@ -5,17 +5,11 @@ import {IERC20} from "@chainlink/vendor/openzeppelin-solidity/v4.8.3/contracts/t import {IKeeperRegistryMaster} from "@chainlink/automation/interfaces/v2_1/IKeeperRegistryMaster.sol"; import {IKeeperRegistrar} from "./interfaces/chainlink/IKeeperRegistrar.sol"; +import {Factory} from "./types/DataTypes.sol"; + import {RoboSaverVirtualModule} from "./RoboSaverVirtualModule.sol"; contract RoboSaverVirtualModuleFactory { - /*////////////////////////////////////////////////////////////////////////// - DATA TYPES - //////////////////////////////////////////////////////////////////////////*/ - struct VirtualModuleDetails { - address virtualModuleAddress; - uint256 upkeepId; - } - /*////////////////////////////////////////////////////////////////////////// CONSTANTS //////////////////////////////////////////////////////////////////////////*/ @@ -29,7 +23,7 @@ contract RoboSaverVirtualModuleFactory { //////////////////////////////////////////////////////////////////////////*/ // card -> (module address, upkeep id) - mapping(address => VirtualModuleDetails) public virtualModules; + mapping(address => Factory.VirtualModuleDetails) public virtualModules; /*////////////////////////////////////////////////////////////////////////// EVENTS @@ -102,7 +96,8 @@ contract RoboSaverVirtualModuleFactory { upkeepId_ = CL_REGISTRAR.registerUpkeep(registrationParams); if (upkeepId_ == 0) revert UpkeepZero(); - virtualModules[msg.sender] = VirtualModuleDetails({virtualModuleAddress: _virtualModule, upkeepId: upkeepId_}); + virtualModules[msg.sender] = + Factory.VirtualModuleDetails({virtualModuleAddress: _virtualModule, upkeepId: upkeepId_}); } /// @notice Converts an address to a string diff --git a/src/types/DataTypes.sol b/src/types/DataTypes.sol new file mode 100644 index 0000000..f44f2ba --- /dev/null +++ b/src/types/DataTypes.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +/// @notice Namespace for the structs used in {RoboSaverVirtualModule}. +library VirtualModule { + /// @notice Enum representing the different types of pool actions + /// @custom:value0 WITHDRAW Withdraw $EURe from the pool to the card + /// @custom:value1 DEPOSIT Deposit $EURe from the card into the pool + /// @custom:value2 CLOSE Close the pool position by withdrawing all to $EURe + /// @custom:value3 EXEC_QUEUE_POOL_ACTION Execute the queued pool action + enum PoolAction { + WITHDRAW, + DEPOSIT, + CLOSE, + EXEC_QUEUE_POOL_ACTION + } + + /// @notice Struct representing the data needed to execute a queued transaction + /// @dev Nonce allows us to determine if the transaction queued originated from this virtual module + /// @param nonce The nonce of the queued transaction + /// @param target The address of the target contract + /// @param payload The payload of the transaction to be executed on the target contract + struct QueuedTx { + uint256 nonce; + address target; + bytes payload; + } +} + +/// @notice Namespace for the structs used in {RoboSaverVirtualModuleFactory}. +library Factory { + /// @notice Struct representing the data details of each registered virtual module in the Chainlink automation service + /// @param virtualModuleAddress The address of the virtual module + /// @param upkeepId The ID of the upkeep registered in Chainlink for the virtual module + struct VirtualModuleDetails { + address virtualModuleAddress; + uint256 upkeepId; + } +} diff --git a/test/integration/AdjustPoolTest.t.sol b/test/integration/AdjustPoolTest.t.sol index 0316069..54110aa 100644 --- a/test/integration/AdjustPoolTest.t.sol +++ b/test/integration/AdjustPoolTest.t.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.25; import {BaseFixture} from "../BaseFixture.sol"; +import {VirtualModule} from "../../src/types/DataTypes.sol"; import {RoboSaverVirtualModule} from "../../src/RoboSaverVirtualModule.sol"; contract AdjustPoolTest is BaseFixture { @@ -23,7 +24,7 @@ contract AdjustPoolTest is BaseFixture { // keeper trying to exec for no reason `adjustPool` while checker is false vm.prank(roboModule.keeper()); vm.expectRevert(abi.encodeWithSelector(RoboSaverVirtualModule.ExternalTxIsQueued.selector)); - roboModule.performUpkeep(abi.encode(RoboSaverVirtualModule.PoolAction.DEPOSIT, 2e18)); + roboModule.performUpkeep(abi.encode(VirtualModule.PoolAction.DEPOSIT, 2e18)); } function test_RevertWhen_VirtualModuleIsDisabled() public { @@ -33,7 +34,7 @@ contract AdjustPoolTest is BaseFixture { vm.prank(roboModule.keeper()); vm.expectRevert(abi.encodeWithSelector(RoboSaverVirtualModule.VirtualModuleNotEnabled.selector)); - roboModule.performUpkeep(abi.encode(RoboSaverVirtualModule.PoolAction.DEPOSIT, 2e18)); + roboModule.performUpkeep(abi.encode(VirtualModule.PoolAction.DEPOSIT, 2e18)); } function test_When_QueueHasExpiredTxs() public { @@ -58,7 +59,7 @@ contract AdjustPoolTest is BaseFixture { // 3. trigger a normal flow (includes cleanup + queuing of a deposit) vm.prank(keeper); - roboModule.performUpkeep(abi.encode(RoboSaverVirtualModule.PoolAction.DEPOSIT, 2e18)); + roboModule.performUpkeep(abi.encode(VirtualModule.PoolAction.DEPOSIT, 2e18)); // asserts the clean up its checked, since it triggered `txNonce++` assertGt(delayModule.txNonce(), txNonceBeforeCleanup); diff --git a/test/integration/ClosePoolTest.t.sol b/test/integration/ClosePoolTest.t.sol index 5bcbdf2..beea662 100644 --- a/test/integration/ClosePoolTest.t.sol +++ b/test/integration/ClosePoolTest.t.sol @@ -5,7 +5,7 @@ import {IERC20} from "@gnosispay-kit/interfaces/IERC20.sol"; import {BaseFixture} from "../BaseFixture.sol"; -import {RoboSaverVirtualModule} from "../../src/RoboSaverVirtualModule.sol"; +import {VirtualModule} from "../../src/types/DataTypes.sol"; contract ClosePoolTest is BaseFixture { function testClosePool() public { @@ -14,9 +14,9 @@ contract ClosePoolTest is BaseFixture { // balance=240, dailyAllowance=200, buffer=50 // deposit 100 vm.startPrank(keeper); - roboModule.performUpkeep(abi.encode(RoboSaverVirtualModule.PoolAction.DEPOSIT, 100e18)); + roboModule.performUpkeep(abi.encode(VirtualModule.PoolAction.DEPOSIT, 100e18)); vm.warp(block.timestamp + COOLDOWN_PERIOD); - roboModule.performUpkeep(abi.encode(RoboSaverVirtualModule.PoolAction.EXEC_QUEUE_POOL_ACTION, 1)); + roboModule.performUpkeep(abi.encode(VirtualModule.PoolAction.EXEC_QUEUE_POOL_ACTION, 1)); vm.stopPrank(); // // set buffer to > dailyAllowance + ~poolBalance @@ -26,15 +26,14 @@ contract ClosePoolTest is BaseFixture { // this should now trigger a pool close (bool canExec, bytes memory execPayload) = roboModule.checkUpkeep(""); assertTrue(canExec); - (RoboSaverVirtualModule.PoolAction _action,) = - abi.decode(execPayload, (RoboSaverVirtualModule.PoolAction, uint256)); - assertEq(uint8(_action), uint8(RoboSaverVirtualModule.PoolAction.CLOSE)); + (VirtualModule.PoolAction _action,) = abi.decode(execPayload, (VirtualModule.PoolAction, uint256)); + assertEq(uint8(_action), uint8(VirtualModule.PoolAction.CLOSE)); // exec it and check if pool is closed vm.startPrank(keeper); roboModule.performUpkeep(execPayload); vm.warp(block.timestamp + COOLDOWN_PERIOD); - roboModule.performUpkeep(abi.encode(RoboSaverVirtualModule.PoolAction.EXEC_QUEUE_POOL_ACTION, 0)); + roboModule.performUpkeep(abi.encode(VirtualModule.PoolAction.EXEC_QUEUE_POOL_ACTION, 0)); assertEq(IERC20(BPT_STEUR_EURE).balanceOf(address(safe)), 0); } } diff --git a/test/integration/TopupBptTest.t.sol b/test/integration/TopupBptTest.t.sol index a050a95..4c52fc3 100644 --- a/test/integration/TopupBptTest.t.sol +++ b/test/integration/TopupBptTest.t.sol @@ -13,7 +13,7 @@ import {Enum} from "../../lib/delay-module/node_modules/@gnosis.pm/safe-contract import {IEURe} from "../../src/interfaces/eure/IEURe.sol"; -import {RoboSaverVirtualModule} from "../../src/RoboSaverVirtualModule.sol"; +import {VirtualModule} from "../../src/types/DataTypes.sol"; contract TopupBptTest is BaseFixture { function testTopupBpt() public { @@ -25,14 +25,12 @@ contract TopupBptTest is BaseFixture { uint256 initialBptBal = IERC20(BPT_STEUR_EURE).balanceOf(address(safe)); (bool canExec, bytes memory execPayload) = roboModule.checkUpkeep(""); - (RoboSaverVirtualModule.PoolAction _action, uint256 _amount) = - abi.decode(execPayload, (RoboSaverVirtualModule.PoolAction, uint256)); + (VirtualModule.PoolAction _action, uint256 _amount) = + abi.decode(execPayload, (VirtualModule.PoolAction, uint256)); // since initially it was minted 1000 it should be way above the buffer assertTrue(canExec, "CanExec: not executable"); - assertEq( - uint8(_action), uint8(RoboSaverVirtualModule.PoolAction.DEPOSIT), "PoolAction: not depositing into the pool" - ); + assertEq(uint8(_action), uint8(VirtualModule.PoolAction.DEPOSIT), "PoolAction: not depositing into the pool"); uint256 bptOutExpected = _getBptOutExpected(_amount); @@ -63,7 +61,7 @@ contract TopupBptTest is BaseFixture { // 1. eure exact appproval to `BALANCER_VAULT` // 2. join the pool single sided with the excess vm.prank(keeper); - roboModule.performUpkeep(abi.encode(RoboSaverVirtualModule.PoolAction.EXEC_QUEUE_POOL_ACTION, 0)); + roboModule.performUpkeep(abi.encode(VirtualModule.PoolAction.EXEC_QUEUE_POOL_ACTION, 0)); // ensure default values at `queuedTx` after execution _assertPostDefaultValuesNextTxExec(); diff --git a/test/integration/TopupEUReTest.t.sol b/test/integration/TopupEUReTest.t.sol index aa44a4d..281f31a 100644 --- a/test/integration/TopupEUReTest.t.sol +++ b/test/integration/TopupEUReTest.t.sol @@ -9,7 +9,7 @@ import "@balancer-v2/interfaces/contracts/vault/IVault.sol"; import {Enum} from "../../lib/delay-module/node_modules/@gnosis.pm/safe-contracts/contracts/common/Enum.sol"; -import {RoboSaverVirtualModule} from "../../src/RoboSaverVirtualModule.sol"; +import {VirtualModule} from "../../src/types/DataTypes.sol"; contract TopupTest is BaseFixture { // @note ref for error codes: https://docs.balancer.fi/reference/contracts/error-codes.html#error-codes @@ -29,13 +29,11 @@ contract TopupTest is BaseFixture { uint256 initialEureBal = IERC20(EURE).balanceOf(address(safe)); (bool canExec, bytes memory execPayload) = roboModule.checkUpkeep(""); - (RoboSaverVirtualModule.PoolAction _action, uint256 _deficit) = - abi.decode(execPayload, (RoboSaverVirtualModule.PoolAction, uint256)); + (VirtualModule.PoolAction _action, uint256 _deficit) = + abi.decode(execPayload, (VirtualModule.PoolAction, uint256)); assertTrue(canExec, "CanExec: not executable"); - assertEq( - uint8(_action), uint8(RoboSaverVirtualModule.PoolAction.WITHDRAW), "PoolAction: not withdrawal from pool" - ); + assertEq(uint8(_action), uint8(VirtualModule.PoolAction.WITHDRAW), "PoolAction: not withdrawal from pool"); // calc via Balancer Queries the max BPT amount to withdraw uint256 maxBPTAmountIn = _getMaxBptInExpected(_deficit, initialBptBal); @@ -63,7 +61,7 @@ contract TopupTest is BaseFixture { _assertPreStorageValuesNextTxExec(address(roboModule.BALANCER_VAULT()), abi.decode(entries[1].data, (bytes))); vm.prank(keeper); - roboModule.performUpkeep(abi.encode(RoboSaverVirtualModule.PoolAction.EXEC_QUEUE_POOL_ACTION, 0)); + roboModule.performUpkeep(abi.encode(VirtualModule.PoolAction.EXEC_QUEUE_POOL_ACTION, 0)); // ensure default values at `queuedTx` after execution _assertPostDefaultValuesNextTxExec(); diff --git a/test/unit/CheckerTest.t.sol b/test/unit/CheckerTest.t.sol index bdca727..e2ee987 100644 --- a/test/unit/CheckerTest.t.sol +++ b/test/unit/CheckerTest.t.sol @@ -7,7 +7,7 @@ import {IERC20} from "@gnosispay-kit/interfaces/IERC20.sol"; import {Enum} from "../../lib/delay-module/node_modules/@gnosis.pm/safe-contracts/contracts/common/Enum.sol"; -import {RoboSaverVirtualModule} from "../../src/RoboSaverVirtualModule.sol"; +import {VirtualModule} from "../../src/types/DataTypes.sol"; contract CheckerTest is BaseFixture { function testChecker_When_TopupIsRequired() public { @@ -22,14 +22,10 @@ contract CheckerTest is BaseFixture { delayModule.executeNextTx(EURE, 0, payload, Enum.Operation.Call); (bool canExec, bytes memory execPayload) = roboModule.checkUpkeep(""); - (RoboSaverVirtualModule.PoolAction _action, uint256 _amount) = - abi.decode(execPayload, (RoboSaverVirtualModule.PoolAction, uint256)); + (VirtualModule.PoolAction _action, uint256 _amount) = + abi.decode(execPayload, (VirtualModule.PoolAction, uint256)); assertTrue(canExec, "CanExec: not executable"); - assertEq( - uint8(_action), - uint8(RoboSaverVirtualModule.PoolAction.WITHDRAW), - "PoolAction: not withdrawing from the pool" - ); + assertEq(uint8(_action), uint8(VirtualModule.PoolAction.WITHDRAW), "PoolAction: not withdrawing from the pool"); assertGt(_amount, 0); } @@ -68,7 +64,7 @@ contract CheckerTest is BaseFixture { function testChecker_When_internalTxIsQueued() public { // 1. assert that internal tx is being queued and within cooldown vm.prank(keeper); - roboModule.performUpkeep(abi.encode(RoboSaverVirtualModule.PoolAction.DEPOSIT, 1000)); + roboModule.performUpkeep(abi.encode(VirtualModule.PoolAction.DEPOSIT, 1000)); (bool canExec, bytes memory execPayload) = roboModule.checkUpkeep(""); assertFalse(canExec); @@ -87,9 +83,9 @@ contract CheckerTest is BaseFixture { (canExec, execPayload) = roboModule.checkUpkeep(""); assertTrue(canExec); - (RoboSaverVirtualModule.PoolAction _action, uint256 _amount) = - abi.decode(execPayload, (RoboSaverVirtualModule.PoolAction, uint256)); - assertEq(uint8(_action), uint8(RoboSaverVirtualModule.PoolAction.EXEC_QUEUE_POOL_ACTION)); + (VirtualModule.PoolAction _action, uint256 _amount) = + abi.decode(execPayload, (VirtualModule.PoolAction, uint256)); + assertEq(uint8(_action), uint8(VirtualModule.PoolAction.EXEC_QUEUE_POOL_ACTION)); assertEq(_amount, 0); } diff --git a/test/unit/InformativeMethodsTest.t.sol b/test/unit/InformativeMethodsTest.t.sol index 3eb89d1..8f89c3b 100644 --- a/test/unit/InformativeMethodsTest.t.sol +++ b/test/unit/InformativeMethodsTest.t.sol @@ -3,8 +3,6 @@ pragma solidity ^0.8.25; import {BaseFixture} from "../BaseFixture.sol"; -import {RoboSaverVirtualModule} from "../../src/RoboSaverVirtualModule.sol"; - /// @notice Suite is focus mainly in the following methods for coverage: /// - `RoboSaverVirtualModule.name()` /// - `RoboSaverVirtualModule.version()` From 095e0fc86853c69a88d4f48aeca2bea1827c31fa Mon Sep 17 00:00:00 2001 From: Petrovska Date: Fri, 28 Jun 2024 16:45:05 +0900 Subject: [PATCH 17/24] feat: verify the delay & roles module matches with the caller --- src/RoboSaverVirtualModule.sol | 37 +++++++++++++++++---------- src/RoboSaverVirtualModuleFactory.sol | 22 +++++++++++++++- 2 files changed, 44 insertions(+), 15 deletions(-) diff --git a/src/RoboSaverVirtualModule.sol b/src/RoboSaverVirtualModule.sol index 91b7030..f5cab6f 100644 --- a/src/RoboSaverVirtualModule.sol +++ b/src/RoboSaverVirtualModule.sol @@ -166,8 +166,9 @@ contract RoboSaverVirtualModule is delayModule = IDelayModifier(_delayModule); rolesModule = IRolesModifier(_rolesModule); - buffer = _buffer; - slippage = _slippage; + + _setBuffer(_buffer); + _setSlippage(_slippage); CARD = delayModule.avatar(); @@ -220,23 +221,13 @@ contract RoboSaverVirtualModule is /// @notice Assigns a new value for the buffer responsible for deciding when there is a surplus /// @param _buffer The value of the new buffer function setBuffer(uint256 _buffer) external onlyAdmin { - if (_buffer == 0) revert ZeroUintValue(); - - uint256 oldBuffer = buffer; - buffer = _buffer; - - emit SetBuffer(msg.sender, oldBuffer, buffer); + _setBuffer(_buffer); } /// @notice Adjust the maximum slippage the user is comfortable with /// @param _slippage The value of the new slippage in bps (so 10_000 is 100%) function setSlippage(uint16 _slippage) external onlyAdmin { - if (_slippage >= MAX_BPS) revert TooHighBps(); - - uint16 oldSlippage = slippage; - slippage = _slippage; - - emit SetSlippage(msg.sender, oldSlippage, slippage); + _setSlippage(_slippage); } /// @notice Check if there is a surplus or deficit of $EURe on the card @@ -459,4 +450,22 @@ contract RoboSaverVirtualModule is anyExpiredTxs_ = true; } } + + function _setSlippage(uint16 _slippage) internal { + if (_slippage >= MAX_BPS) revert TooHighBps(); + + uint16 oldSlippage = slippage; + slippage = _slippage; + + emit SetSlippage(msg.sender, oldSlippage, slippage); + } + + function _setBuffer(uint256 _buffer) internal { + if (_buffer == 0) revert ZeroUintValue(); + + uint256 oldBuffer = buffer; + buffer = _buffer; + + emit SetBuffer(msg.sender, oldBuffer, buffer); + } } diff --git a/src/RoboSaverVirtualModuleFactory.sol b/src/RoboSaverVirtualModuleFactory.sol index 5f19aaf..d70c5ec 100644 --- a/src/RoboSaverVirtualModuleFactory.sol +++ b/src/RoboSaverVirtualModuleFactory.sol @@ -5,6 +5,9 @@ import {IERC20} from "@chainlink/vendor/openzeppelin-solidity/v4.8.3/contracts/t import {IKeeperRegistryMaster} from "@chainlink/automation/interfaces/v2_1/IKeeperRegistryMaster.sol"; import {IKeeperRegistrar} from "./interfaces/chainlink/IKeeperRegistrar.sol"; +import {IDelayModifier} from "./interfaces/delayModule/IDelayModifier.sol"; +import {IRolesModifier} from "@gnosispay-kit/interfaces/IRolesModifier.sol"; + import {Factory} from "./types/DataTypes.sol"; import {RoboSaverVirtualModule} from "./RoboSaverVirtualModule.sol"; @@ -35,6 +38,8 @@ contract RoboSaverVirtualModuleFactory { //////////////////////////////////////////////////////////////////////////*/ error UpkeepZero(); + error CallerNotMatchingAvatar(string moduleName, address caller); + /*////////////////////////////////////////////////////////////////////////// CONSTRUCTOR //////////////////////////////////////////////////////////////////////////*/ @@ -55,7 +60,8 @@ contract RoboSaverVirtualModuleFactory { function createVirtualModule(address _delayModule, address _rolesModule, uint256 _buffer, uint16 _slippage) external { - // @todo sanity checks on the inputs! + /// @dev verification of `buffer` & `slippage` is done in the constructor of the virtual module + _verifyVirtualModuleCreationArgs(_delayModule, _rolesModule); // uses `msg.sender` as a salt to make the deployed address of the virtual module deterministic address virtualModule = address( @@ -77,6 +83,20 @@ contract RoboSaverVirtualModuleFactory { INTERNAL METHODS //////////////////////////////////////////////////////////////////////////*/ + /// @notice Verifies the arguments passed to the virtual module creation + /// @param _delayModule The address of the delay module + /// @param _rolesModule The address of the roles module + function _verifyVirtualModuleCreationArgs(address _delayModule, address _rolesModule) internal view { + // verify that delay module avatar & `msg.sender` matches + if (IDelayModifier(_delayModule).avatar() != msg.sender) { + revert CallerNotMatchingAvatar("DelayModule", msg.sender); + } + // verify that the roles module avatar & `msg.sender` matches + if (IRolesModifier(_rolesModule).avatar() != msg.sender) { + revert CallerNotMatchingAvatar("RolesModule", msg.sender); + } + } + /// @notice Registers the virtual module in the Chainlink Keeper Registry /// @param _virtualModule The address of the virtual module to be registered function _registerRoboSaverVirtualModule(address _virtualModule) internal returns (uint256 upkeepId_) { From c7923601e866e450317d88ebae24d282e5647cd4 Mon Sep 17 00:00:00 2001 From: Petrovska Date: Fri, 28 Jun 2024 16:49:29 +0900 Subject: [PATCH 18/24] test: add hit on Factory constructor --- test/BaseFixture.sol | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/BaseFixture.sol b/test/BaseFixture.sol index 3144689..89fe57d 100644 --- a/test/BaseFixture.sol +++ b/test/BaseFixture.sol @@ -150,8 +150,6 @@ contract BaseFixture is Test { // @note is it neccesary for our setup: assign roles, scope target, scope function? - // @note pendant of wiring up a keeper service here at some point - vm.prank(SAFE_EOA_SIGNER); rolesModule.transferOwnership(address(bouncerContract)); @@ -160,6 +158,9 @@ contract BaseFixture is Test { deal(BPT_STEUR_EURE, address(safe), EURE_TO_MINT); + // assert here constructor action in the {RoboSaverVirtualModuleFactory} for a hit + assertEq(IERC20(LINK).allowance(address(roboModuleFactory), address(CL_REGISTRAR)), type(uint256).max); + _labelKeyContracts(); } From d3ea943ba9d84515e622f26a24ccaadf90ced957 Mon Sep 17 00:00:00 2001 From: Petrovska Date: Fri, 28 Jun 2024 17:20:18 +0900 Subject: [PATCH 19/24] test: cover reverts cases related to verification --- src/RoboSaverVirtualModuleFactory.sol | 3 +- test/integration/FactoryTest.t.sol | 43 +++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 test/integration/FactoryTest.t.sol diff --git a/src/RoboSaverVirtualModuleFactory.sol b/src/RoboSaverVirtualModuleFactory.sol index d70c5ec..3e8f5d4 100644 --- a/src/RoboSaverVirtualModuleFactory.sol +++ b/src/RoboSaverVirtualModuleFactory.sol @@ -45,6 +45,7 @@ contract RoboSaverVirtualModuleFactory { //////////////////////////////////////////////////////////////////////////*/ constructor() { + /// @dev max approval to be able to handle smoothly upkeep creations and top-ups LINK.approve(address(CL_REGISTRAR), type(uint256).max); } @@ -104,7 +105,7 @@ contract RoboSaverVirtualModuleFactory { name: string.concat(RoboSaverVirtualModule(_virtualModule).name(), "-", _addressToString(msg.sender)), encryptedEmail: "", upkeepContract: _virtualModule, - gasLimit: 2_000_000, // @todo optimise from the gas logs the right value. perhaps we are overshooting + gasLimit: 2_000_000, adminAddress: address(this), // @note the factory is the admin triggerType: 0, checkData: "", diff --git a/test/integration/FactoryTest.t.sol b/test/integration/FactoryTest.t.sol new file mode 100644 index 0000000..d9c97dc --- /dev/null +++ b/test/integration/FactoryTest.t.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {BaseFixture} from "../BaseFixture.sol"; + +import {Delay} from "@delay-module/Delay.sol"; +import {Roles} from "@roles-module/Roles.sol"; + +import {RoboSaverVirtualModuleFactory} from "../../src/RoboSaverVirtualModuleFactory.sol"; + +contract FactoryTest is BaseFixture { + Delay dummyDelayModule; + Roles dummyRolesModule; + + function test_ReverWhen_AvatarDoesNotMatch() public { + address randomAvatar = address(545_495); + // deploy dummy delay and roles modules + dummyRolesModule = new Roles(SAFE_EOA_SIGNER, randomAvatar, randomAvatar); + dummyDelayModule = new Delay(randomAvatar, randomAvatar, randomAvatar, COOLDOWN_PERIOD, EXPIRATION_PERIOD); + + vm.startPrank(address(safe)); + + vm.expectRevert( + abi.encodeWithSelector( + RoboSaverVirtualModuleFactory.CallerNotMatchingAvatar.selector, "DelayModule", address(safe) + ) + ); + roboModuleFactory.createVirtualModule(address(dummyRolesModule), address(rolesModule), EURE_BUFFER, SLIPPAGE); + + vm.expectRevert( + abi.encodeWithSelector( + RoboSaverVirtualModuleFactory.CallerNotMatchingAvatar.selector, "RolesModule", address(safe) + ) + ); + roboModuleFactory.createVirtualModule(address(delayModule), address(dummyRolesModule), EURE_BUFFER, SLIPPAGE); + vm.stopPrank(); + } + + function test_RevertWhen_UpkeepReturnsZero() public { + // seems that only `upkeepId_` could be return null in case that it is not "autoApprove" + // ref: https://github.com/smartcontractkit/chainlink/blob/develop/contracts/src/v0.8/automation/v2_0/KeeperRegistrar2_0.sol#L376 + } +} From 8f199785d6d174634a59ae7cb1ee62e2c4427459 Mon Sep 17 00:00:00 2001 From: Petrovska Date: Fri, 28 Jun 2024 17:45:43 +0900 Subject: [PATCH 20/24] test: commence structure for `test_RevertWhen_UpkeepReturnsZero` --- test/BaseFixture.sol | 14 +++++++++++++- test/integration/FactoryTest.t.sol | 21 +++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/test/BaseFixture.sol b/test/BaseFixture.sol index 89fe57d..6013d50 100644 --- a/test/BaseFixture.sol +++ b/test/BaseFixture.sol @@ -166,20 +166,32 @@ contract BaseFixture is Test { /// @dev Labels key contracts for tracing function _labelKeyContracts() internal { + vm.label(address(safe), "GNOSIS_SAFE"); + // robosaver module factory + vm.label(address(roboModuleFactory), "ROBO_MODULE_FACTORY"); + // tokens vm.label(EURE, "EURE"); vm.label(WETH, "WETH"); vm.label(LINK, "LINK"); - vm.label(address(safe), "GNOSIS_SAFE"); + // gnosis pay modules infrastructure vm.label(address(delayModule), "DELAY_MODULE"); vm.label(address(bouncerContract), "BOUNCER_CONTRACT"); vm.label(address(rolesModule), "ROLES_MODULE"); vm.label(address(roboModule), "ROBO_MODULE"); + // balancer vm.label(BPT_STEUR_EURE, "BPT_STEUR_EURE"); vm.label(address(roboModule.BALANCER_VAULT()), "BALANCER_VAULT"); + // chainlink vm.label(address(CL_REGISTRY), "CL_REGISTRY"); vm.label(address(CL_REGISTRAR), "CL_REGISTRAR"); } + function _getDeterministicAddress(bytes memory bytecode, bytes32 _salt) internal view returns (address) { + bytes32 hash = keccak256(abi.encodePacked(bytes1(0xff), address(this), _salt, keccak256(bytecode))); + + return address(uint160(uint256(hash))); + } + function _addressToString(address _addr) internal pure returns (string memory) { bytes32 value = bytes32(uint256(uint160(_addr))); bytes memory alphabet = "0123456789abcdef"; diff --git a/test/integration/FactoryTest.t.sol b/test/integration/FactoryTest.t.sol index d9c97dc..4815e7f 100644 --- a/test/integration/FactoryTest.t.sol +++ b/test/integration/FactoryTest.t.sol @@ -6,6 +6,9 @@ import {BaseFixture} from "../BaseFixture.sol"; import {Delay} from "@delay-module/Delay.sol"; import {Roles} from "@roles-module/Roles.sol"; +import {IKeeperRegistrar} from "../../src/interfaces/chainlink/IKeeperRegistrar.sol"; + +import {RoboSaverVirtualModule} from "../../src/RoboSaverVirtualModule.sol"; import {RoboSaverVirtualModuleFactory} from "../../src/RoboSaverVirtualModuleFactory.sol"; contract FactoryTest is BaseFixture { @@ -37,7 +40,25 @@ contract FactoryTest is BaseFixture { } function test_RevertWhen_UpkeepReturnsZero() public { + bytes memory creationCode = abi.encodePacked(type(RoboSaverVirtualModule).creationCode); + bytes32 salt = keccak256(abi.encodePacked(address(safe))); + address deterministicVirtualModuleAddress = _getDeterministicAddress(creationCode, salt); + // seems that only `upkeepId_` could be return null in case that it is not "autoApprove" // ref: https://github.com/smartcontractkit/chainlink/blob/develop/contracts/src/v0.8/automation/v2_0/KeeperRegistrar2_0.sol#L376 + IKeeperRegistrar.RegistrationParams memory params = IKeeperRegistrar.RegistrationParams({ + name: string.concat("RoboSaverVirtualModule-EURE", "-", _addressToString(address(safe))), + encryptedEmail: "", + upkeepContract: deterministicVirtualModuleAddress, + gasLimit: 2_000_000, + adminAddress: address(roboModuleFactory), + triggerType: 0, + checkData: "", + triggerConfig: "", + offchainConfig: "", + amount: 200e18 + }); + + // @todo pendant of implementing properly in another PR ensuring proper storage manipulation } } From 96f6762b615c0162d0b86eb7f256402072ff7672 Mon Sep 17 00:00:00 2001 From: Petrovska Date: Fri, 28 Jun 2024 17:55:18 +0900 Subject: [PATCH 21/24] natspec: RoboSaverVirtualModuleFactory --- src/RoboSaverVirtualModuleFactory.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/RoboSaverVirtualModuleFactory.sol b/src/RoboSaverVirtualModuleFactory.sol index 3e8f5d4..aa99ac6 100644 --- a/src/RoboSaverVirtualModuleFactory.sol +++ b/src/RoboSaverVirtualModuleFactory.sol @@ -12,6 +12,9 @@ import {Factory} from "./types/DataTypes.sol"; import {RoboSaverVirtualModule} from "./RoboSaverVirtualModule.sol"; +/// @title RoboSaverVirtualModuleFactory +/// @author onchainification.xyz +/// @notice Factory contract creates an unique {RoboSaverVirtualModule} per Gnosis Pay card, and registers it in the Chainlink Keeper Registry contract RoboSaverVirtualModuleFactory { /*////////////////////////////////////////////////////////////////////////// CONSTANTS From c57a5fb61a909bc5e2937356ab5ca9ef67619dad Mon Sep 17 00:00:00 2001 From: Gosuto Inzasheru Date: Fri, 28 Jun 2024 11:36:31 +0200 Subject: [PATCH 22/24] test: make codecov exclude script file --- script/GnosisPayInfraDeployment.s.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/script/GnosisPayInfraDeployment.s.sol b/script/GnosisPayInfraDeployment.s.sol index 5a6c58b..f987a9f 100644 --- a/script/GnosisPayInfraDeployment.s.sol +++ b/script/GnosisPayInfraDeployment.s.sol @@ -86,4 +86,7 @@ contract GnosisPayInfraDeployment is Script { // 1. Enable delay & roles modules on the safe (to be exec from the safe) // 2. Enable RoboSaverVirtualModule on the delay module (to be exec from the safe) } + + // contracts that have this method are excluded by codecov + function test() public {} } From 03d92fa13d142b3856948a1764091cd4752a80cb Mon Sep 17 00:00:00 2001 From: Gosuto Inzasheru Date: Fri, 28 Jun 2024 11:38:15 +0200 Subject: [PATCH 23/24] test: exclude all scripts via codecov config --- .github/codecov.yaml | 2 ++ script/GnosisPayInfraDeployment.s.sol | 3 --- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/codecov.yaml b/.github/codecov.yaml index 5e4016b..ca27e1b 100644 --- a/.github/codecov.yaml +++ b/.github/codecov.yaml @@ -9,3 +9,5 @@ coverage: status: project: off patch: off +ignore: + - "script" # exclude files in the script directory diff --git a/script/GnosisPayInfraDeployment.s.sol b/script/GnosisPayInfraDeployment.s.sol index f987a9f..5a6c58b 100644 --- a/script/GnosisPayInfraDeployment.s.sol +++ b/script/GnosisPayInfraDeployment.s.sol @@ -86,7 +86,4 @@ contract GnosisPayInfraDeployment is Script { // 1. Enable delay & roles modules on the safe (to be exec from the safe) // 2. Enable RoboSaverVirtualModule on the delay module (to be exec from the safe) } - - // contracts that have this method are excluded by codecov - function test() public {} } From 21ec122bea6ee025fd76fc0d34c1d7b9e1c08459 Mon Sep 17 00:00:00 2001 From: Gosuto Inzasheru Date: Fri, 28 Jun 2024 16:08:30 +0200 Subject: [PATCH 24/24] test: also ignore `test/` for codecov reports --- .github/codecov.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/codecov.yaml b/.github/codecov.yaml index ca27e1b..1213b64 100644 --- a/.github/codecov.yaml +++ b/.github/codecov.yaml @@ -9,5 +9,6 @@ coverage: status: project: off patch: off -ignore: - - "script" # exclude files in the script directory +ignore: # exclude these folders from the report + - "script" + - "test"