From 9707b9e26f62e67bcfb855921a6ea5cf7d987683 Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Wed, 3 Apr 2024 18:43:09 -0400 Subject: [PATCH 01/40] rewards claim guard --- .../LlamaRewardsClaimGuard.sol | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/rewards-claimer/LlamaRewardsClaimGuard.sol diff --git a/src/rewards-claimer/LlamaRewardsClaimGuard.sol b/src/rewards-claimer/LlamaRewardsClaimGuard.sol new file mode 100644 index 0000000..02a628a --- /dev/null +++ b/src/rewards-claimer/LlamaRewardsClaimGuard.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import {ILlamaActionGuard} from "src/interfaces/ILlamaActionGuard.sol"; +import {ActionInfo} from "src/lib/Structs.sol"; + +/// @title Llama Rewards Claim Guard +/// @author Llama (devsdosomething@llama.xyz) +/// @notice A guard that only allows the `RewardsClaimer` contract to be delegate-called from a Llama account's execute +/// function. +/// @dev This guard should be used to protect the `execute` function in the `LlamaAccount` contract +contract LlamaRewardsClaimGuard is ILlamaActionGuard { + /// @dev Thrown if the call is not authorized. + error UnauthorizedCall(address target, bytes4 selector, bool withDelegatecall); + + /// @notice The address of the `RewardsClaimer` contract. + address public rewardsClaimer; + + /// @dev Initializes the Llama rewards claim guard. + constructor(address _rewardsClaimer) { + rewardsClaimer = _rewardsClaimer; + } + + /// @inheritdoc ILlamaActionGuard + function validateActionCreation(ActionInfo calldata actionInfo) external view { + // Decode the action calldata to get the LlamaAccount execute target, call type and call data. + (address target, bool withDelegatecall,, bytes memory data) = + abi.decode(actionInfo.data[4:], (address, bool, uint256, bytes)); + bytes4 selector = bytes4(data); + + // Check if the target is the rewards claimer and the call type is a delegatecall. + if (target != rewardsClaimer || !withDelegatecall) revert UnauthorizedCall(target, selector, withDelegatecall); + } + + /// @inheritdoc ILlamaActionGuard + function validatePreActionExecution(ActionInfo calldata actionInfo) external pure {} + + /// @inheritdoc ILlamaActionGuard + function validatePostActionExecution(ActionInfo calldata actionInfo) external pure {} +} From 442abb1795db322309467cb8833f68fc319469e4 Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Wed, 3 Apr 2024 18:49:03 -0400 Subject: [PATCH 02/40] natspec --- src/rewards-claimer/LlamaRewardsClaimGuard.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/rewards-claimer/LlamaRewardsClaimGuard.sol b/src/rewards-claimer/LlamaRewardsClaimGuard.sol index 02a628a..b3c7293 100644 --- a/src/rewards-claimer/LlamaRewardsClaimGuard.sol +++ b/src/rewards-claimer/LlamaRewardsClaimGuard.sol @@ -6,8 +6,7 @@ import {ActionInfo} from "src/lib/Structs.sol"; /// @title Llama Rewards Claim Guard /// @author Llama (devsdosomething@llama.xyz) -/// @notice A guard that only allows the `RewardsClaimer` contract to be delegate-called from a Llama account's execute -/// function. +/// @notice A guard that only allows the `RewardsClaimer.claimAllRewards` to be delegate-called /// @dev This guard should be used to protect the `execute` function in the `LlamaAccount` contract contract LlamaRewardsClaimGuard is ILlamaActionGuard { /// @dev Thrown if the call is not authorized. From cd889cf0cc474fe77b68fd9ded0b06b807f1a8bb Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Wed, 3 Apr 2024 21:45:55 -0400 Subject: [PATCH 03/40] sort of working --- .../LlamaRewardsClaimAccountExtension.sol | 58 +++++++++++++++++++ .../LlamaRewardsClaimGuard.sol | 10 ++-- .../LlamaRewardsClaimStorage.sol | 51 ++++++++++++++++ 3 files changed, 114 insertions(+), 5 deletions(-) create mode 100644 src/rewards-claimer/LlamaRewardsClaimAccountExtension.sol create mode 100644 src/rewards-claimer/LlamaRewardsClaimStorage.sol diff --git a/src/rewards-claimer/LlamaRewardsClaimAccountExtension.sol b/src/rewards-claimer/LlamaRewardsClaimAccountExtension.sol new file mode 100644 index 0000000..18ea4c9 --- /dev/null +++ b/src/rewards-claimer/LlamaRewardsClaimAccountExtension.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import {LlamaUtils} from "src/lib/LlamaUtils.sol"; +import {LlamaRewardsClaimStorage} from "src/rewards-claimer/LlamaRewardsClaimStorage.sol"; + +/// @title Llama Rewards Claim Account Extension +/// @author Llama (devsdosomething@llama.xyz) +/// @notice An account extension that claims rewards on behalf of the Llama account. +/// @dev This contract should be delegate-called from a Llama account. +contract LlamaRewardsClaimAccountExtension { + /// @dev Struct to hold target data. + struct TargetData { + address target; // The target contract. + uint256 value; // The target call value. + bytes data; // The target call data. + } + + /// @dev The call did not succeed. + /// @param index Index of the target data being called. + /// @param revertData Data returned by the called function. + error CallReverted(uint256 index, bytes revertData); + + /// @dev Thrown if the target-selector is not authorized. + error UnauthorizedTargetSelector(address target, bytes4 selector); + + /// @notice The rewards claim storage contract. + LlamaRewardsClaimStorage public immutable REWARDS_CLAIM_STORAGE; + + /// @dev Initializes the Llama rewards claim account extenstion. + constructor(LlamaRewardsClaimStorage rewardsClaimStorage) { + REWARDS_CLAIM_STORAGE = rewardsClaimStorage; + } + + /// @notice Claims rewards from the target contracts. + /// @param targetData The target data to claim rewards from. + /// @return returnData The return data from the target calls. + function claimRewards(TargetData[] memory targetData) external returns (bytes[] memory returnData) { + uint256 length = targetData.length; + returnData = new bytes[](length); + for (uint256 i = 0; i < length; LlamaUtils.uncheckedIncrement(i)) { + address target = targetData[i].target; + uint256 value = targetData[i].value; + bytes memory callData = targetData[i].data; + bytes4 selector = bytes4(callData); + + // Check if the target-selector is authorized. + if (!REWARDS_CLAIM_STORAGE.authorizedTargetSelectors(target, selector)) { + revert UnauthorizedTargetSelector(target, selector); + } + + // Execute the call. + (bool success, bytes memory result) = target.call{value: value}(callData); + if (!success) revert CallReverted(i, result); + returnData[i] = result; + } + } +} diff --git a/src/rewards-claimer/LlamaRewardsClaimGuard.sol b/src/rewards-claimer/LlamaRewardsClaimGuard.sol index b3c7293..f625c32 100644 --- a/src/rewards-claimer/LlamaRewardsClaimGuard.sol +++ b/src/rewards-claimer/LlamaRewardsClaimGuard.sol @@ -6,18 +6,18 @@ import {ActionInfo} from "src/lib/Structs.sol"; /// @title Llama Rewards Claim Guard /// @author Llama (devsdosomething@llama.xyz) -/// @notice A guard that only allows the `RewardsClaimer.claimAllRewards` to be delegate-called +/// @notice A guard that only allows the `RewardsClaimer.claimRewards` to be delegate-called /// @dev This guard should be used to protect the `execute` function in the `LlamaAccount` contract contract LlamaRewardsClaimGuard is ILlamaActionGuard { /// @dev Thrown if the call is not authorized. error UnauthorizedCall(address target, bytes4 selector, bool withDelegatecall); /// @notice The address of the `RewardsClaimer` contract. - address public rewardsClaimer; + address public immutable REWARDS_CLAIMER; /// @dev Initializes the Llama rewards claim guard. - constructor(address _rewardsClaimer) { - rewardsClaimer = _rewardsClaimer; + constructor(address rewardsClaimer) { + REWARDS_CLAIMER = rewardsClaimer; } /// @inheritdoc ILlamaActionGuard @@ -28,7 +28,7 @@ contract LlamaRewardsClaimGuard is ILlamaActionGuard { bytes4 selector = bytes4(data); // Check if the target is the rewards claimer and the call type is a delegatecall. - if (target != rewardsClaimer || !withDelegatecall) revert UnauthorizedCall(target, selector, withDelegatecall); + if (target != REWARDS_CLAIMER || !withDelegatecall) revert UnauthorizedCall(target, selector, withDelegatecall); } /// @inheritdoc ILlamaActionGuard diff --git a/src/rewards-claimer/LlamaRewardsClaimStorage.sol b/src/rewards-claimer/LlamaRewardsClaimStorage.sol new file mode 100644 index 0000000..89ebe36 --- /dev/null +++ b/src/rewards-claimer/LlamaRewardsClaimStorage.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import {LlamaUtils} from "src/lib/LlamaUtils.sol"; + +/// @title Llama Rewards Claim Storage +/// @author Llama (devsdosomething@llama.xyz) +/// @notice The storage contract for the LlamaRewardsClaimAccountExtension contract. +/// @dev This is a separate storage contract to prevent storage collisions with the Llama account. +contract LlamaRewardsClaimStorage { + /// @dev Struct to hold authorized target-selectors. + struct TargetSelectorAuthorization { + address target; // The target contract. + bytes4 selector; // The selector of the function being called. + bool isAuthorized; // Is the target-selector authorized. + } + + /// @dev Only callable by a Llama instance's executor. + error OnlyLlama(); + + /// @notice Emitted when a target-selector is authorized. + event TargetSelectorAuthorized(address indexed target, bytes4 indexed selector, bool isAuthorized); + + /// @notice The Llama instance's executor. + address public immutable LLAMA_EXECUTOR; + + /// @notice Mapping of all authorized target-selectors. + mapping(address target => mapping(bytes4 selector => bool isAuthorized)) public authorizedTargetSelectors; + + /// @dev Initializes the Llama rewards claim storage. + constructor(address llamaExecutor, TargetSelectorAuthorization[] memory data) { + LLAMA_EXECUTOR = llamaExecutor; + _setAuthorizedTargetSelectors(data); + } + + /// @notice Sets the authorized target-selectors. + /// @param data The target-selectors to authorize. + function setAuthorizedTargetSelectors(TargetSelectorAuthorization[] memory data) external { + if (msg.sender != LLAMA_EXECUTOR) revert OnlyLlama(); + _setAuthorizedTargetSelectors(data); + } + + /// @dev Sets the authorized target-selectors. + function _setAuthorizedTargetSelectors(TargetSelectorAuthorization[] memory data) internal { + uint256 length = data.length; + for (uint256 i = 0; i < length; LlamaUtils.uncheckedIncrement(i)) { + authorizedTargetSelectors[data[i].target][data[i].selector] = data[i].isAuthorized; + emit TargetSelectorAuthorized(data[i].target, data[i].selector, data[i].isAuthorized); + } + } +} From 34b0ffe9f6ea72dd473a55a77805055e73065e85 Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Wed, 3 Apr 2024 21:54:15 -0400 Subject: [PATCH 04/40] sort of working --- src/common/LlamaBaseAccountExtension.sol | 23 +++++++++++++++++++ .../LlamaRewardsClaimAccountExtension.sol | 5 ++-- 2 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 src/common/LlamaBaseAccountExtension.sol diff --git a/src/common/LlamaBaseAccountExtension.sol b/src/common/LlamaBaseAccountExtension.sol new file mode 100644 index 0000000..e4126fe --- /dev/null +++ b/src/common/LlamaBaseAccountExtension.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +/// @dev This account extension is a template for creating new account extensions, and should not be used directly. +abstract contract LlamaBaseAccountExtension { + /// @dev Thrown if you try to CALL a function that has the `onlyDelegatecall` modifier. + error OnlyDelegateCall(); + + /// @dev Add this to your account extension's methods to ensure the account extension can only be used via + /// delegatecall, and not a regular call. + modifier onlyDelegateCall() { + if (address(this) == SELF) revert OnlyDelegateCall(); + _; + } + + /// @dev Address of the account extension contract. We save it off because during a delegatecall `address(this)` + /// refers to the caller's address, not this account extension's address. + address internal immutable SELF; + + constructor() { + SELF = address(this); + } +} diff --git a/src/rewards-claimer/LlamaRewardsClaimAccountExtension.sol b/src/rewards-claimer/LlamaRewardsClaimAccountExtension.sol index 18ea4c9..9fd3a3b 100644 --- a/src/rewards-claimer/LlamaRewardsClaimAccountExtension.sol +++ b/src/rewards-claimer/LlamaRewardsClaimAccountExtension.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.23; +import {LlamaBaseAccountExtension} from "src/common/LlamaBaseAccountExtension.sol"; import {LlamaUtils} from "src/lib/LlamaUtils.sol"; import {LlamaRewardsClaimStorage} from "src/rewards-claimer/LlamaRewardsClaimStorage.sol"; @@ -8,7 +9,7 @@ import {LlamaRewardsClaimStorage} from "src/rewards-claimer/LlamaRewardsClaimSto /// @author Llama (devsdosomething@llama.xyz) /// @notice An account extension that claims rewards on behalf of the Llama account. /// @dev This contract should be delegate-called from a Llama account. -contract LlamaRewardsClaimAccountExtension { +contract LlamaRewardsClaimAccountExtension is LlamaBaseAccountExtension { /// @dev Struct to hold target data. struct TargetData { address target; // The target contract. @@ -35,7 +36,7 @@ contract LlamaRewardsClaimAccountExtension { /// @notice Claims rewards from the target contracts. /// @param targetData The target data to claim rewards from. /// @return returnData The return data from the target calls. - function claimRewards(TargetData[] memory targetData) external returns (bytes[] memory returnData) { + function claimRewards(TargetData[] memory targetData) external onlyDelegateCall returns (bytes[] memory returnData) { uint256 length = targetData.length; returnData = new bytes[](length); for (uint256 i = 0; i < length; LlamaUtils.uncheckedIncrement(i)) { From 2e74c527115cb4e09fc08c71610e8bd44a4cc6e0 Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Wed, 3 Apr 2024 22:01:20 -0400 Subject: [PATCH 05/40] working --- src/rewards-claimer/LlamaRewardsClaimGuard.sol | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/rewards-claimer/LlamaRewardsClaimGuard.sol b/src/rewards-claimer/LlamaRewardsClaimGuard.sol index f625c32..3e3a5ff 100644 --- a/src/rewards-claimer/LlamaRewardsClaimGuard.sol +++ b/src/rewards-claimer/LlamaRewardsClaimGuard.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.23; import {ILlamaActionGuard} from "src/interfaces/ILlamaActionGuard.sol"; import {ActionInfo} from "src/lib/Structs.sol"; +import {LlamaRewardsClaimAccountExtension} from "src/rewards-claimer/LlamaRewardsClaimAccountExtension.sol"; /// @title Llama Rewards Claim Guard /// @author Llama (devsdosomething@llama.xyz) @@ -12,11 +13,11 @@ contract LlamaRewardsClaimGuard is ILlamaActionGuard { /// @dev Thrown if the call is not authorized. error UnauthorizedCall(address target, bytes4 selector, bool withDelegatecall); - /// @notice The address of the `RewardsClaimer` contract. - address public immutable REWARDS_CLAIMER; + /// @notice The address of the `RewardsClaimer` account extension. + LlamaRewardsClaimAccountExtension public immutable REWARDS_CLAIMER; /// @dev Initializes the Llama rewards claim guard. - constructor(address rewardsClaimer) { + constructor(LlamaRewardsClaimAccountExtension rewardsClaimer) { REWARDS_CLAIMER = rewardsClaimer; } @@ -27,8 +28,11 @@ contract LlamaRewardsClaimGuard is ILlamaActionGuard { abi.decode(actionInfo.data[4:], (address, bool, uint256, bytes)); bytes4 selector = bytes4(data); - // Check if the target is the rewards claimer and the call type is a delegatecall. - if (target != REWARDS_CLAIMER || !withDelegatecall) revert UnauthorizedCall(target, selector, withDelegatecall); + // Check if the target is the rewards claimer, selector is `claimRewards` and the call type is a delegatecall. + if ( + target != address(REWARDS_CLAIMER) || selector != LlamaRewardsClaimAccountExtension.claimRewards.selector + || !withDelegatecall + ) revert UnauthorizedCall(target, selector, withDelegatecall); } /// @inheritdoc ILlamaActionGuard From 5eb0198dfe436efaf6157161c21f860d8307ffa8 Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Thu, 4 Apr 2024 15:47:38 -0400 Subject: [PATCH 06/40] description --- src/rewards-claimer/LlamaRewardsClaimGuard.sol | 2 +- src/rewards-claimer/LlamaRewardsClaimStorage.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rewards-claimer/LlamaRewardsClaimGuard.sol b/src/rewards-claimer/LlamaRewardsClaimGuard.sol index 3e3a5ff..3545ea5 100644 --- a/src/rewards-claimer/LlamaRewardsClaimGuard.sol +++ b/src/rewards-claimer/LlamaRewardsClaimGuard.sol @@ -7,7 +7,7 @@ import {LlamaRewardsClaimAccountExtension} from "src/rewards-claimer/LlamaReward /// @title Llama Rewards Claim Guard /// @author Llama (devsdosomething@llama.xyz) -/// @notice A guard that only allows the `RewardsClaimer.claimRewards` to be delegate-called +/// @notice A guard that only allows the `LlamaRewardsClaimAccountExtension.claimRewards` to be delegate-called /// @dev This guard should be used to protect the `execute` function in the `LlamaAccount` contract contract LlamaRewardsClaimGuard is ILlamaActionGuard { /// @dev Thrown if the call is not authorized. diff --git a/src/rewards-claimer/LlamaRewardsClaimStorage.sol b/src/rewards-claimer/LlamaRewardsClaimStorage.sol index 89ebe36..60ac13f 100644 --- a/src/rewards-claimer/LlamaRewardsClaimStorage.sol +++ b/src/rewards-claimer/LlamaRewardsClaimStorage.sol @@ -5,7 +5,7 @@ import {LlamaUtils} from "src/lib/LlamaUtils.sol"; /// @title Llama Rewards Claim Storage /// @author Llama (devsdosomething@llama.xyz) -/// @notice The storage contract for the LlamaRewardsClaimAccountExtension contract. +/// @notice The storage contract for the `LlamaRewardsClaimAccountExtension` contract. /// @dev This is a separate storage contract to prevent storage collisions with the Llama account. contract LlamaRewardsClaimStorage { /// @dev Struct to hold authorized target-selectors. From 77acfa0e1f4bd203b414057d2715b0481ce0eec9 Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Thu, 4 Apr 2024 16:34:36 -0400 Subject: [PATCH 07/40] initial script --- justfile | 4 +++ script/DeployLlamaRewardsClaimer.s.sol | 35 ++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 script/DeployLlamaRewardsClaimer.s.sol diff --git a/justfile b/justfile index 0fa9749..5f6f344 100644 --- a/justfile +++ b/justfile @@ -28,4 +28,8 @@ dry-run-deploy-voting-module: (run-deploy-voting-module-script '') deploy-voting-module: (run-deploy-voting-module-script '--broadcast --verify') +dry-run-deploy-rewards-claimer: (run-script 'DeployLlamaRewardsClaimer') + +deploy-rewards-claimer: (run-script 'DeployLlamaRewardsClaimer' '--broadcast --verify --slow') + verify: (run-script 'DeployLlamaTokenVotingFactory' '--verify --resume') diff --git a/script/DeployLlamaRewardsClaimer.s.sol b/script/DeployLlamaRewardsClaimer.s.sol new file mode 100644 index 0000000..cef6e40 --- /dev/null +++ b/script/DeployLlamaRewardsClaimer.s.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +import {Script} from "forge-std/Script.sol"; + +import {DeployUtils} from "script/DeployUtils.sol"; +import {LlamaRewardsClaimAccountExtension} from "src/rewards-claimer/LlamaRewardsClaimAccountExtension.sol"; +import {LlamaRewardsClaimGuard} from "src/rewards-claimer/LlamaRewardsClaimGuard.sol"; +import {LlamaRewardsClaimStorage} from "src/rewards-claimer/LlamaRewardsClaimStorage.sol"; + +contract DeployLlamaRewardsClaimer is Script { + // Rewards claim contracts. + LlamaRewardsClaimStorage rewardsClaimStorage; + LlamaRewardsClaimAccountExtension rewardsClaimAccountExtension; + LlamaRewardsClaimGuard rewardsClaimGuard; + + function run() public { + DeployUtils.print(string.concat("Deploying Llama rewards claimer contracts to chain:", vm.toString(block.chainid))); + + vm.broadcast(); + rewardsClaimStorage = + new LlamaRewardsClaimStorage(address(0), new LlamaRewardsClaimStorage.TargetSelectorAuthorization[](0)); + DeployUtils.print(string.concat(" LlamaRewardsClaimStorage: ", vm.toString(address(rewardsClaimStorage)))); + + vm.broadcast(); + rewardsClaimAccountExtension = new LlamaRewardsClaimAccountExtension(rewardsClaimStorage); + DeployUtils.print( + string.concat(" LlamaRewardsClaimAccountExtension: ", vm.toString(address(rewardsClaimAccountExtension))) + ); + + vm.broadcast(); + rewardsClaimGuard = new LlamaRewardsClaimGuard(rewardsClaimAccountExtension); + DeployUtils.print(string.concat(" LlamaRewardsClaimGuard: ", vm.toString(address(rewardsClaimGuard)))); + } +} From 518cc1b025f598a36edff8eb608ed62d8c9bb813 Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Thu, 4 Apr 2024 17:47:53 -0400 Subject: [PATCH 08/40] script input --- justfile | 6 ++- script/DeployLlamaRewardsClaimer.s.sol | 44 +++++++++++++++++-- .../input/11155111/rewardsClaimerConfig.json | 12 +++++ 3 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 script/input/11155111/rewardsClaimerConfig.json diff --git a/justfile b/justfile index 5f6f344..9787e0c 100644 --- a/justfile +++ b/justfile @@ -28,8 +28,10 @@ dry-run-deploy-voting-module: (run-deploy-voting-module-script '') deploy-voting-module: (run-deploy-voting-module-script '--broadcast --verify') -dry-run-deploy-rewards-claimer: (run-script 'DeployLlamaRewardsClaimer') +run-deploy-rewards-claimer-script flags: (run-script 'DeployLlamaRewardsClaimer' flags '--sig "run(string)"' '"rewardsClaimerConfig.json"') -deploy-rewards-claimer: (run-script 'DeployLlamaRewardsClaimer' '--broadcast --verify --slow') +dry-run-deploy-rewards-claimer: (run-deploy-rewards-claimer-script '') + +deploy-rewards-claimer: (run-deploy-rewards-claimer-script '--broadcast --verify --slow') verify: (run-script 'DeployLlamaTokenVotingFactory' '--verify --resume') diff --git a/script/DeployLlamaRewardsClaimer.s.sol b/script/DeployLlamaRewardsClaimer.s.sol index cef6e40..1725f7a 100644 --- a/script/DeployLlamaRewardsClaimer.s.sol +++ b/script/DeployLlamaRewardsClaimer.s.sol @@ -1,25 +1,40 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.23; -import {Script} from "forge-std/Script.sol"; +import {Script, stdJson} from "forge-std/Script.sol"; import {DeployUtils} from "script/DeployUtils.sol"; + import {LlamaRewardsClaimAccountExtension} from "src/rewards-claimer/LlamaRewardsClaimAccountExtension.sol"; import {LlamaRewardsClaimGuard} from "src/rewards-claimer/LlamaRewardsClaimGuard.sol"; import {LlamaRewardsClaimStorage} from "src/rewards-claimer/LlamaRewardsClaimStorage.sol"; contract DeployLlamaRewardsClaimer is Script { + using stdJson for string; + + struct TargetSelectorAuthorizationInputs { + // Attributes need to be in alphabetical order so JSON decodes properly. + string comment; + bool isAuthorized; + bytes selector; + address target; + } + // Rewards claim contracts. LlamaRewardsClaimStorage rewardsClaimStorage; LlamaRewardsClaimAccountExtension rewardsClaimAccountExtension; LlamaRewardsClaimGuard rewardsClaimGuard; - function run() public { + function run(string memory configFile) public { + string memory jsonInput = DeployUtils.readScriptInput(configFile); + + address llamaExecutor = jsonInput.readAddress(".llamaExecutor"); + LlamaRewardsClaimStorage.TargetSelectorAuthorization[] memory data = readTargetSelectorAuthorizations(jsonInput); + DeployUtils.print(string.concat("Deploying Llama rewards claimer contracts to chain:", vm.toString(block.chainid))); vm.broadcast(); - rewardsClaimStorage = - new LlamaRewardsClaimStorage(address(0), new LlamaRewardsClaimStorage.TargetSelectorAuthorization[](0)); + rewardsClaimStorage = new LlamaRewardsClaimStorage(llamaExecutor, data); DeployUtils.print(string.concat(" LlamaRewardsClaimStorage: ", vm.toString(address(rewardsClaimStorage)))); vm.broadcast(); @@ -32,4 +47,25 @@ contract DeployLlamaRewardsClaimer is Script { rewardsClaimGuard = new LlamaRewardsClaimGuard(rewardsClaimAccountExtension); DeployUtils.print(string.concat(" LlamaRewardsClaimGuard: ", vm.toString(address(rewardsClaimGuard)))); } + + function readTargetSelectorAuthorizations(string memory jsonInput) + internal + pure + returns (LlamaRewardsClaimStorage.TargetSelectorAuthorization[] memory) + { + bytes memory data = jsonInput.parseRaw(".initialTargetSelectorAuthorizations"); + + TargetSelectorAuthorizationInputs[] memory rawConfigs = abi.decode(data, (TargetSelectorAuthorizationInputs[])); + + LlamaRewardsClaimStorage.TargetSelectorAuthorization[] memory configs = + new LlamaRewardsClaimStorage.TargetSelectorAuthorization[](rawConfigs.length); + + for (uint256 i = 0; i < rawConfigs.length; i++) { + configs[i].target = rawConfigs[i].target; + configs[i].selector = bytes4(rawConfigs[i].selector); + configs[i].isAuthorized = rawConfigs[i].isAuthorized; + } + + return configs; + } } diff --git a/script/input/11155111/rewardsClaimerConfig.json b/script/input/11155111/rewardsClaimerConfig.json new file mode 100644 index 0000000..c47550b --- /dev/null +++ b/script/input/11155111/rewardsClaimerConfig.json @@ -0,0 +1,12 @@ +{ + "comment": "This is an example rewards claimer deployment on Sepolia.", + "llamaExecutor": "0x0000000000000000000000000000000000000000", + "initialTargetSelectorAuthorizations": [ + { + "comment": "Target::Selector", + "isAuthorized": false, + "selector": "0x00000000", + "target": "0x0000000000000000000000000000000000000000" + } + ] +} From f81b30c9b50730b8b3a4161b0fb13a222703a236 Mon Sep 17 00:00:00 2001 From: Austin Green Date: Thu, 4 Apr 2024 17:41:12 -1000 Subject: [PATCH 09/40] use checked increment --- src/rewards-claimer/LlamaRewardsClaimStorage.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rewards-claimer/LlamaRewardsClaimStorage.sol b/src/rewards-claimer/LlamaRewardsClaimStorage.sol index 60ac13f..4b1dbfd 100644 --- a/src/rewards-claimer/LlamaRewardsClaimStorage.sol +++ b/src/rewards-claimer/LlamaRewardsClaimStorage.sol @@ -43,7 +43,7 @@ contract LlamaRewardsClaimStorage { /// @dev Sets the authorized target-selectors. function _setAuthorizedTargetSelectors(TargetSelectorAuthorization[] memory data) internal { uint256 length = data.length; - for (uint256 i = 0; i < length; LlamaUtils.uncheckedIncrement(i)) { + for (uint256 i = 0; i < length; i++) { authorizedTargetSelectors[data[i].target][data[i].selector] = data[i].isAuthorized; emit TargetSelectorAuthorized(data[i].target, data[i].selector, data[i].isAuthorized); } From 7f03607a9048a3387b7af00b5671a0bdaf3d4d6c Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Fri, 5 Apr 2024 13:38:25 -0400 Subject: [PATCH 10/40] fix --- src/rewards-claimer/LlamaRewardsClaimAccountExtension.sol | 2 +- src/rewards-claimer/LlamaRewardsClaimGuard.sol | 2 +- src/rewards-claimer/LlamaRewardsClaimStorage.sol | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/rewards-claimer/LlamaRewardsClaimAccountExtension.sol b/src/rewards-claimer/LlamaRewardsClaimAccountExtension.sol index 9fd3a3b..71dfbb7 100644 --- a/src/rewards-claimer/LlamaRewardsClaimAccountExtension.sol +++ b/src/rewards-claimer/LlamaRewardsClaimAccountExtension.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; +pragma solidity 0.8.23; import {LlamaBaseAccountExtension} from "src/common/LlamaBaseAccountExtension.sol"; import {LlamaUtils} from "src/lib/LlamaUtils.sol"; diff --git a/src/rewards-claimer/LlamaRewardsClaimGuard.sol b/src/rewards-claimer/LlamaRewardsClaimGuard.sol index 3545ea5..13ecd7b 100644 --- a/src/rewards-claimer/LlamaRewardsClaimGuard.sol +++ b/src/rewards-claimer/LlamaRewardsClaimGuard.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; +pragma solidity 0.8.23; import {ILlamaActionGuard} from "src/interfaces/ILlamaActionGuard.sol"; import {ActionInfo} from "src/lib/Structs.sol"; diff --git a/src/rewards-claimer/LlamaRewardsClaimStorage.sol b/src/rewards-claimer/LlamaRewardsClaimStorage.sol index 4b1dbfd..deeb3c3 100644 --- a/src/rewards-claimer/LlamaRewardsClaimStorage.sol +++ b/src/rewards-claimer/LlamaRewardsClaimStorage.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; +pragma solidity 0.8.23; import {LlamaUtils} from "src/lib/LlamaUtils.sol"; @@ -43,7 +43,7 @@ contract LlamaRewardsClaimStorage { /// @dev Sets the authorized target-selectors. function _setAuthorizedTargetSelectors(TargetSelectorAuthorization[] memory data) internal { uint256 length = data.length; - for (uint256 i = 0; i < length; i++) { + for (uint256 i = 0; i < length; i = LlamaUtils.uncheckedIncrement(i)) { authorizedTargetSelectors[data[i].target][data[i].selector] = data[i].isAuthorized; emit TargetSelectorAuthorized(data[i].target, data[i].selector, data[i].isAuthorized); } From 881b6481ff06e9371b529d0b68164eecc20641e9 Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Fri, 5 Apr 2024 17:50:11 -0400 Subject: [PATCH 11/40] isAuthorization true --- script/DeployLlamaRewardsClaimer.s.sol | 3 +-- script/input/11155111/rewardsClaimerConfig.json | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/script/DeployLlamaRewardsClaimer.s.sol b/script/DeployLlamaRewardsClaimer.s.sol index 1725f7a..d3ea264 100644 --- a/script/DeployLlamaRewardsClaimer.s.sol +++ b/script/DeployLlamaRewardsClaimer.s.sol @@ -15,7 +15,6 @@ contract DeployLlamaRewardsClaimer is Script { struct TargetSelectorAuthorizationInputs { // Attributes need to be in alphabetical order so JSON decodes properly. string comment; - bool isAuthorized; bytes selector; address target; } @@ -63,7 +62,7 @@ contract DeployLlamaRewardsClaimer is Script { for (uint256 i = 0; i < rawConfigs.length; i++) { configs[i].target = rawConfigs[i].target; configs[i].selector = bytes4(rawConfigs[i].selector); - configs[i].isAuthorized = rawConfigs[i].isAuthorized; + configs[i].isAuthorized = true; } return configs; diff --git a/script/input/11155111/rewardsClaimerConfig.json b/script/input/11155111/rewardsClaimerConfig.json index c47550b..90efb70 100644 --- a/script/input/11155111/rewardsClaimerConfig.json +++ b/script/input/11155111/rewardsClaimerConfig.json @@ -4,7 +4,6 @@ "initialTargetSelectorAuthorizations": [ { "comment": "Target::Selector", - "isAuthorized": false, "selector": "0x00000000", "target": "0x0000000000000000000000000000000000000000" } From 97b0642f3f4b9442f7a8834d02a43a09257f40c2 Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Fri, 5 Apr 2024 18:14:13 -0400 Subject: [PATCH 12/40] test setup --- script/input/1/mockRewardsClaimerConfig.json | 11 ++++++++++ .../LlamaRewardsClaimStorage.t.sol | 22 +++++++++++++++++++ .../LlamaRewardsClaimTestSetup.sol | 22 +++++++++++++++++++ 3 files changed, 55 insertions(+) create mode 100644 script/input/1/mockRewardsClaimerConfig.json create mode 100644 test/rewards-claimer/LlamaRewardsClaimStorage.t.sol create mode 100644 test/rewards-claimer/LlamaRewardsClaimTestSetup.sol diff --git a/script/input/1/mockRewardsClaimerConfig.json b/script/input/1/mockRewardsClaimerConfig.json new file mode 100644 index 0000000..3510945 --- /dev/null +++ b/script/input/1/mockRewardsClaimerConfig.json @@ -0,0 +1,11 @@ +{ + "comment": "This is a rewards claimer deployment for Forge tests.", + "llamaExecutor": "0xdAf00E9786cABB195a8a1Cf102730863aE94Dd75", + "initialTargetSelectorAuthorizations": [ + { + "comment": "DeadBeef.withdraw()", + "selector": "0x3ccfd60b", + "target": "0x00000000000000000000000000000000deadbeef" + } + ] +} diff --git a/test/rewards-claimer/LlamaRewardsClaimStorage.t.sol b/test/rewards-claimer/LlamaRewardsClaimStorage.t.sol new file mode 100644 index 0000000..6717a8d --- /dev/null +++ b/test/rewards-claimer/LlamaRewardsClaimStorage.t.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import {Test, console2} from "forge-std/Test.sol"; + +import {LlamaRewardsClaimTestSetup} from "test/rewards-claimer/LlamaRewardsClaimTestSetup.sol"; + +contract LlamaRewardsClaimStorageTest is LlamaRewardsClaimTestSetup { + function setUp() public override { + LlamaRewardsClaimTestSetup.setUp(); + } +} + +contract Constructor is LlamaRewardsClaimStorageTest { + function test_SetsLlamaExecutor() public { + assertEq(address(rewardsClaimStorage.LLAMA_EXECUTOR()), address(EXECUTOR)); + } + + function test_TargetSelectorAuthorization() public { + assertTrue(rewardsClaimStorage.authorizedTargetSelectors(address(0xdeadbeef), bytes4(keccak256("withdraw()")))); + } +} diff --git a/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol b/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol new file mode 100644 index 0000000..82492a2 --- /dev/null +++ b/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import {Test, console2} from "forge-std/Test.sol"; +import {Vm} from "forge-std/Vm.sol"; + +import {LlamaPeripheryTestSetup} from "test/LlamaPeripheryTestSetup.sol"; + +import {DeployLlamaRewardsClaimer} from "script/DeployLlamaRewardsClaimer.s.sol"; + +contract LlamaRewardsClaimTestSetup is LlamaPeripheryTestSetup, DeployLlamaRewardsClaimer { + function setUp() public virtual override { + LlamaPeripheryTestSetup.setUp(); + + // Deploy the Llama Token Voting factory and logic contracts. + DeployLlamaRewardsClaimer.run("mockRewardsClaimerConfig.json"); + } + + // ========================= + // ======== Helpers ======== + // ========================= +} From f8651954e09d6d0014b49c60d82e110ae3e4a705 Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Fri, 5 Apr 2024 18:24:18 -0400 Subject: [PATCH 13/40] rewardsclaimstorage tests --- .../LlamaRewardsClaimStorage.t.sol | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/test/rewards-claimer/LlamaRewardsClaimStorage.t.sol b/test/rewards-claimer/LlamaRewardsClaimStorage.t.sol index 6717a8d..8d1a2e1 100644 --- a/test/rewards-claimer/LlamaRewardsClaimStorage.t.sol +++ b/test/rewards-claimer/LlamaRewardsClaimStorage.t.sol @@ -5,7 +5,11 @@ import {Test, console2} from "forge-std/Test.sol"; import {LlamaRewardsClaimTestSetup} from "test/rewards-claimer/LlamaRewardsClaimTestSetup.sol"; +import {LlamaRewardsClaimStorage} from "src/rewards-claimer/LlamaRewardsClaimStorage.sol"; + contract LlamaRewardsClaimStorageTest is LlamaRewardsClaimTestSetup { + event TargetSelectorAuthorized(address indexed target, bytes4 indexed selector, bool isAuthorized); + function setUp() public override { LlamaRewardsClaimTestSetup.setUp(); } @@ -20,3 +24,31 @@ contract Constructor is LlamaRewardsClaimStorageTest { assertTrue(rewardsClaimStorage.authorizedTargetSelectors(address(0xdeadbeef), bytes4(keccak256("withdraw()")))); } } + +contract SetAuthorizedTargetSelectors is LlamaRewardsClaimStorageTest { + function test_RevertIf_CallerIsNotLlama() public { + LlamaRewardsClaimStorage.TargetSelectorAuthorization[] memory data = + new LlamaRewardsClaimStorage.TargetSelectorAuthorization[](1); + data[0] = + LlamaRewardsClaimStorage.TargetSelectorAuthorization(address(0xdeadbeef), bytes4(keccak256("withdraw()")), false); + + vm.expectRevert(LlamaRewardsClaimStorage.OnlyLlama.selector); + rewardsClaimStorage.setAuthorizedTargetSelectors(data); + } + + function test_SetAuthorizedTargetSelectors() public { + LlamaRewardsClaimStorage.TargetSelectorAuthorization[] memory data = + new LlamaRewardsClaimStorage.TargetSelectorAuthorization[](1); + data[0] = + LlamaRewardsClaimStorage.TargetSelectorAuthorization(address(0xdeadbeef), bytes4(keccak256("withdraw()")), false); + + assertTrue(rewardsClaimStorage.authorizedTargetSelectors(address(0xdeadbeef), bytes4(keccak256("withdraw()")))); + + vm.prank(address(EXECUTOR)); + vm.expectEmit(); + emit TargetSelectorAuthorized(address(0xdeadbeef), bytes4(keccak256("withdraw()")), false); + rewardsClaimStorage.setAuthorizedTargetSelectors(data); + + assertFalse(rewardsClaimStorage.authorizedTargetSelectors(address(0xdeadbeef), bytes4(keccak256("withdraw()")))); + } +} From 0a13c51373784942ec91912319b147b230a4e8b2 Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Fri, 5 Apr 2024 18:26:08 -0400 Subject: [PATCH 14/40] cleanup --- test/rewards-claimer/LlamaRewardsClaimTestSetup.sol | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol b/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol index 82492a2..84a7b66 100644 --- a/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol +++ b/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol @@ -15,8 +15,4 @@ contract LlamaRewardsClaimTestSetup is LlamaPeripheryTestSetup, DeployLlamaRewar // Deploy the Llama Token Voting factory and logic contracts. DeployLlamaRewardsClaimer.run("mockRewardsClaimerConfig.json"); } - - // ========================= - // ======== Helpers ======== - // ========================= } From 3b6f98b4377f06b7a90180959f720d83c684fcf6 Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Fri, 5 Apr 2024 18:45:33 -0400 Subject: [PATCH 15/40] mock rewards contract --- test/mock/MockRewardsContract.sol | 31 +++++++++++++++++++ .../LlamaRewardsClaimTestSetup.sol | 2 +- 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 test/mock/MockRewardsContract.sol diff --git a/test/mock/MockRewardsContract.sol b/test/mock/MockRewardsContract.sol new file mode 100644 index 0000000..01823c8 --- /dev/null +++ b/test/mock/MockRewardsContract.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import {IERC20} from "@openzeppelin/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/token/ERC20/utils/SafeERC20.sol"; +import {Address} from "@openzeppelin/utils/Address.sol"; + +contract MockRewardsContract { + using SafeERC20 for IERC20; + + error OnlyRewardClaimer(); + + modifier onlyRewardClaimer() { + if (msg.sender != REWARD_CLAIMER) revert OnlyRewardClaimer(); + _; + } + + address public immutable REWARD_CLAIMER; + + constructor(address rewardClaimer) { + REWARD_CLAIMER = rewardClaimer; + } + + function withdrawETH() external payable onlyRewardClaimer { + Address.sendValue(payable(REWARD_CLAIMER), address(this).balance); + } + + function withdrawERC20(IERC20 token) external onlyRewardClaimer { + token.safeTransfer(REWARD_CLAIMER, token.balanceOf(address(this))); + } +} diff --git a/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol b/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol index 84a7b66..10b5225 100644 --- a/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol +++ b/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol @@ -12,7 +12,7 @@ contract LlamaRewardsClaimTestSetup is LlamaPeripheryTestSetup, DeployLlamaRewar function setUp() public virtual override { LlamaPeripheryTestSetup.setUp(); - // Deploy the Llama Token Voting factory and logic contracts. + // Deploy the Llama rewards claim contracts. DeployLlamaRewardsClaimer.run("mockRewardsClaimerConfig.json"); } } From d3de0e98aa8cfca467d5c17c6915bc67a33e93cc Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Fri, 5 Apr 2024 19:00:45 -0400 Subject: [PATCH 16/40] setup --- .../LlamaRewardsClaimTestSetup.sol | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol b/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol index 10b5225..c0e8d11 100644 --- a/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol +++ b/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol @@ -3,16 +3,44 @@ pragma solidity ^0.8.23; import {Test, console2} from "forge-std/Test.sol"; import {Vm} from "forge-std/Vm.sol"; +import {StdUtils} from "forge-std/StdUtils.sol"; + +import {IERC20} from "@openzeppelin/token/ERC20/IERC20.sol"; import {LlamaPeripheryTestSetup} from "test/LlamaPeripheryTestSetup.sol"; +import {MockRewardsContract} from "test/mock/MockRewardsContract.sol"; import {DeployLlamaRewardsClaimer} from "script/DeployLlamaRewardsClaimer.s.sol"; contract LlamaRewardsClaimTestSetup is LlamaPeripheryTestSetup, DeployLlamaRewardsClaimer { + IERC20 public constant USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); + function setUp() public virtual override { LlamaPeripheryTestSetup.setUp(); // Deploy the Llama rewards claim contracts. DeployLlamaRewardsClaimer.run("mockRewardsClaimerConfig.json"); + + // Deploy the mock rewards contracts and set LlamaAccount as the reward claimer. + // Deal ETH and UNI to the rewards contracts. + MockRewardsContract rewardsContract1 = new MockRewardsContract(address(ACCOUNT)); + vm.deal(address(rewardsContract1), 1 ether); + deal(address(USDC), address(rewardsContract1), 1 ether); + + MockRewardsContract rewardsContract2 = new MockRewardsContract(address(ACCOUNT)); + vm.deal(address(rewardsContract2), 1 ether); + deal(address(USDC), address(rewardsContract2), 1 ether); + + MockRewardsContract rewardsContract3 = new MockRewardsContract(address(ACCOUNT)); + vm.deal(address(rewardsContract3), 1 ether); + deal(address(USDC), address(rewardsContract3), 1 ether); + + MockRewardsContract rewardsContract4 = new MockRewardsContract(address(ACCOUNT)); + vm.deal(address(rewardsContract4), 1 ether); + deal(address(USDC), address(rewardsContract4), 1 ether); + + MockRewardsContract rewardsContract5 = new MockRewardsContract(address(ACCOUNT)); + vm.deal(address(rewardsContract5), 1 ether); + deal(address(USDC), address(rewardsContract5), 1 ether); } } From 9a84a22e006830c95b7a35562b39354bc3bd8bb7 Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Fri, 5 Apr 2024 19:05:47 -0400 Subject: [PATCH 17/40] setup --- .../LlamaRewardsClaimTestSetup.sol | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol b/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol index c0e8d11..592bdc1 100644 --- a/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol +++ b/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol @@ -12,6 +12,8 @@ import {MockRewardsContract} from "test/mock/MockRewardsContract.sol"; import {DeployLlamaRewardsClaimer} from "script/DeployLlamaRewardsClaimer.s.sol"; +import {LlamaRewardsClaimStorage} from "src/rewards-claimer/LlamaRewardsClaimStorage.sol"; + contract LlamaRewardsClaimTestSetup is LlamaPeripheryTestSetup, DeployLlamaRewardsClaimer { IERC20 public constant USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); @@ -42,5 +44,40 @@ contract LlamaRewardsClaimTestSetup is LlamaPeripheryTestSetup, DeployLlamaRewar MockRewardsContract rewardsContract5 = new MockRewardsContract(address(ACCOUNT)); vm.deal(address(rewardsContract5), 1 ether); deal(address(USDC), address(rewardsContract5), 1 ether); + + // Authorize the rewards contracts to claim rewards. + LlamaRewardsClaimStorage.TargetSelectorAuthorization[] memory data = + new LlamaRewardsClaimStorage.TargetSelectorAuthorization[](10); + data[0] = LlamaRewardsClaimStorage.TargetSelectorAuthorization( + address(rewardsContract1), MockRewardsContract.withdrawETH.selector, true + ); + data[1] = LlamaRewardsClaimStorage.TargetSelectorAuthorization( + address(rewardsContract1), MockRewardsContract.withdrawERC20.selector, true + ); + data[2] = LlamaRewardsClaimStorage.TargetSelectorAuthorization( + address(rewardsContract2), MockRewardsContract.withdrawETH.selector, true + ); + data[3] = LlamaRewardsClaimStorage.TargetSelectorAuthorization( + address(rewardsContract2), MockRewardsContract.withdrawERC20.selector, true + ); + data[4] = LlamaRewardsClaimStorage.TargetSelectorAuthorization( + address(rewardsContract3), MockRewardsContract.withdrawETH.selector, true + ); + data[5] = LlamaRewardsClaimStorage.TargetSelectorAuthorization( + address(rewardsContract3), MockRewardsContract.withdrawERC20.selector, true + ); + data[6] = LlamaRewardsClaimStorage.TargetSelectorAuthorization( + address(rewardsContract4), MockRewardsContract.withdrawETH.selector, true + ); + data[7] = LlamaRewardsClaimStorage.TargetSelectorAuthorization( + address(rewardsContract4), MockRewardsContract.withdrawERC20.selector, true + ); + data[8] = LlamaRewardsClaimStorage.TargetSelectorAuthorization( + address(rewardsContract5), MockRewardsContract.withdrawETH.selector, true + ); + data[9] = LlamaRewardsClaimStorage.TargetSelectorAuthorization( + address(rewardsContract5), MockRewardsContract.withdrawERC20.selector, true + ); + rewardsClaimStorage.setAuthorizedTargetSelectors(data); } } From df991cc2688e21e5c90a8b644414a37af634beea Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Fri, 5 Apr 2024 19:15:57 -0400 Subject: [PATCH 18/40] set guard --- test/rewards-claimer/LlamaRewardsClaimTestSetup.sol | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol b/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol index 592bdc1..a763abe 100644 --- a/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol +++ b/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol @@ -79,5 +79,10 @@ contract LlamaRewardsClaimTestSetup is LlamaPeripheryTestSetup, DeployLlamaRewar address(rewardsContract5), MockRewardsContract.withdrawERC20.selector, true ); rewardsClaimStorage.setAuthorizedTargetSelectors(data); + + // Set the Llama rewards claim guard. + CORE.setGuard( + address(ACCOUNT), bytes4(keccak256("execute(address,bool,uint256,bytes)")), address(rewardsClaimGuard) + ); } } From 1147e5fd15e4146009ce42bcc6071fea0da0bc10 Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Fri, 5 Apr 2024 19:23:32 -0400 Subject: [PATCH 19/40] fix --- test/rewards-claimer/LlamaRewardsClaimTestSetup.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol b/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol index a763abe..2bf1a46 100644 --- a/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol +++ b/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol @@ -45,6 +45,7 @@ contract LlamaRewardsClaimTestSetup is LlamaPeripheryTestSetup, DeployLlamaRewar vm.deal(address(rewardsContract5), 1 ether); deal(address(USDC), address(rewardsContract5), 1 ether); + vm.startPrank(address(EXECUTOR)); // Authorize the rewards contracts to claim rewards. LlamaRewardsClaimStorage.TargetSelectorAuthorization[] memory data = new LlamaRewardsClaimStorage.TargetSelectorAuthorization[](10); @@ -84,5 +85,6 @@ contract LlamaRewardsClaimTestSetup is LlamaPeripheryTestSetup, DeployLlamaRewar CORE.setGuard( address(ACCOUNT), bytes4(keccak256("execute(address,bool,uint256,bytes)")), address(rewardsClaimGuard) ); + vm.stopPrank(); } } From 53048284299de91e280d2d83ef46e14a2dec8ba3 Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Sat, 6 Apr 2024 11:43:40 -0400 Subject: [PATCH 20/40] account extension test --- .../LlamaRewardsClaimGuard.t.sol | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 test/rewards-claimer/LlamaRewardsClaimGuard.t.sol diff --git a/test/rewards-claimer/LlamaRewardsClaimGuard.t.sol b/test/rewards-claimer/LlamaRewardsClaimGuard.t.sol new file mode 100644 index 0000000..e234959 --- /dev/null +++ b/test/rewards-claimer/LlamaRewardsClaimGuard.t.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import {Test, console2} from "forge-std/Test.sol"; + +import {LlamaRewardsClaimTestSetup} from "test/rewards-claimer/LlamaRewardsClaimTestSetup.sol"; + +import {LlamaRewardsClaimGuard} from "src/rewards-claimer/LlamaRewardsClaimGuard.sol"; + +contract LlamaRewardsClaimGuardTest is LlamaRewardsClaimTestSetup { + function setUp() public override { + LlamaRewardsClaimTestSetup.setUp(); + } +} + +contract Constructor is LlamaRewardsClaimGuardTest { + function test_SetsRewardsClaimer() public { + assertEq(address(rewardsClaimGuard.REWARDS_CLAIMER()), address(rewardsClaimAccountExtension)); + } +} + +contract ValidateActionCreation is LlamaRewardsClaimGuardTest {} From 8f7574213fb019d0fa8628cbd4588b7cad35c46e Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Mon, 8 Apr 2024 11:59:28 -0400 Subject: [PATCH 21/40] ILlamaAccount --- src/interfaces/ILlamaAccount.sol | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/interfaces/ILlamaAccount.sol b/src/interfaces/ILlamaAccount.sol index c9d059a..75ff643 100644 --- a/src/interfaces/ILlamaAccount.sol +++ b/src/interfaces/ILlamaAccount.sol @@ -20,4 +20,19 @@ interface ILlamaAccount { /// @return This return statement must be hardcoded to `true` to ensure that initializing an EOA /// (like the zero address) will revert. function initialize(bytes memory config) external returns (bool); + + // -------- Generic Execution -------- + + /// @notice Execute arbitrary calls from the Llama Account. + /// @dev Be careful and intentional while assigning permissions to a policyholder that can create an action to call + /// this function, especially while using the delegatecall functionality as it can lead to arbitrary code execution in + /// the context of this Llama account. + /// @param target The address of the contract to call. + /// @param withDelegatecall Whether to use delegatecall or call. + /// @param value The amount of ETH to send with the call, taken from the Llama Account. + /// @param callData The calldata to pass to the contract. + /// @return The result of the call. + function execute(address target, bool withDelegatecall, uint256 value, bytes calldata callData) + external + returns (bytes memory); } From 8eddba7236c4c3969d8a5fcf7387a71e43592c44 Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Mon, 8 Apr 2024 12:58:57 -0400 Subject: [PATCH 22/40] sort of working --- .../LlamaRewardsClaimGuard.t.sol | 59 +++++++++++++- .../LlamaRewardsClaimTestSetup.sol | 78 +++++++++++++++++-- 2 files changed, 129 insertions(+), 8 deletions(-) diff --git a/test/rewards-claimer/LlamaRewardsClaimGuard.t.sol b/test/rewards-claimer/LlamaRewardsClaimGuard.t.sol index e234959..b080ff1 100644 --- a/test/rewards-claimer/LlamaRewardsClaimGuard.t.sol +++ b/test/rewards-claimer/LlamaRewardsClaimGuard.t.sol @@ -3,8 +3,11 @@ pragma solidity ^0.8.23; import {Test, console2} from "forge-std/Test.sol"; +import {MockRewardsContract} from "test/mock/MockRewardsContract.sol"; import {LlamaRewardsClaimTestSetup} from "test/rewards-claimer/LlamaRewardsClaimTestSetup.sol"; +import {ILlamaAccount} from "src/interfaces/ILlamaAccount.sol"; +import {LlamaRewardsClaimAccountExtension} from "src/rewards-claimer/LlamaRewardsClaimAccountExtension.sol"; import {LlamaRewardsClaimGuard} from "src/rewards-claimer/LlamaRewardsClaimGuard.sol"; contract LlamaRewardsClaimGuardTest is LlamaRewardsClaimTestSetup { @@ -19,4 +22,58 @@ contract Constructor is LlamaRewardsClaimGuardTest { } } -contract ValidateActionCreation is LlamaRewardsClaimGuardTest {} +contract ValidateActionCreation is LlamaRewardsClaimGuardTest { + function _setupClaimRewardsData() public returns (LlamaRewardsClaimAccountExtension.TargetData[] memory targetData) { + targetData = new LlamaRewardsClaimAccountExtension.TargetData[](10); + targetData[0] = LlamaRewardsClaimAccountExtension.TargetData( + address(rewardsContract1), 0, abi.encodeCall(MockRewardsContract.withdrawETH, ()) + ); + targetData[1] = LlamaRewardsClaimAccountExtension.TargetData( + address(rewardsContract1), 0, abi.encodeCall(MockRewardsContract.withdrawERC20, (USDC)) + ); + targetData[2] = LlamaRewardsClaimAccountExtension.TargetData( + address(rewardsContract2), 0, abi.encodeCall(MockRewardsContract.withdrawETH, ()) + ); + targetData[3] = LlamaRewardsClaimAccountExtension.TargetData( + address(rewardsContract2), 0, abi.encodeCall(MockRewardsContract.withdrawERC20, (USDC)) + ); + targetData[4] = LlamaRewardsClaimAccountExtension.TargetData( + address(rewardsContract3), 0, abi.encodeCall(MockRewardsContract.withdrawETH, ()) + ); + targetData[5] = LlamaRewardsClaimAccountExtension.TargetData( + address(rewardsContract3), 0, abi.encodeCall(MockRewardsContract.withdrawERC20, (USDC)) + ); + targetData[6] = LlamaRewardsClaimAccountExtension.TargetData( + address(rewardsContract4), 0, abi.encodeCall(MockRewardsContract.withdrawETH, ()) + ); + targetData[7] = LlamaRewardsClaimAccountExtension.TargetData( + address(rewardsContract4), 0, abi.encodeCall(MockRewardsContract.withdrawERC20, (USDC)) + ); + targetData[8] = LlamaRewardsClaimAccountExtension.TargetData( + address(rewardsContract5), 0, abi.encodeCall(MockRewardsContract.withdrawETH, ()) + ); + targetData[9] = LlamaRewardsClaimAccountExtension.TargetData( + address(rewardsContract5), 0, abi.encodeCall(MockRewardsContract.withdrawERC20, (USDC)) + ); + } + + function test_RevertIf_TargetIsNotRewardsClaimer() public { + LlamaRewardsClaimAccountExtension.TargetData[] memory targetData = _setupClaimRewardsData(); + + address dummyTarget = address(0xdeadbeef); + + bytes memory accountExtensionData = abi.encodeCall(LlamaRewardsClaimAccountExtension.claimRewards, (targetData)); + bytes memory data = abi.encodeCall(ILlamaAccount.execute, (dummyTarget, true, 0, accountExtensionData)); + + vm.prank(coreTeam1); + vm.expectRevert( + abi.encodeWithSelector( + LlamaRewardsClaimGuard.UnauthorizedCall.selector, + dummyTarget, + LlamaRewardsClaimAccountExtension.claimRewards.selector, + true + ) + ); + CORE.createAction(CORE_TEAM_ROLE, STRATEGY, address(ACCOUNT), 0, data, ""); + } +} diff --git a/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol b/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol index 2bf1a46..7492583 100644 --- a/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol +++ b/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol @@ -12,11 +12,23 @@ import {MockRewardsContract} from "test/mock/MockRewardsContract.sol"; import {DeployLlamaRewardsClaimer} from "script/DeployLlamaRewardsClaimer.s.sol"; +import {ILlamaAccount} from "src/interfaces/ILlamaAccount.sol"; +import {ILlamaPolicy} from "src/interfaces/ILlamaPolicy.sol"; +import {ActionInfo} from "src/lib/Structs.sol"; +import {LlamaRewardsClaimAccountExtension} from "src/rewards-claimer/LlamaRewardsClaimAccountExtension.sol"; import {LlamaRewardsClaimStorage} from "src/rewards-claimer/LlamaRewardsClaimStorage.sol"; contract LlamaRewardsClaimTestSetup is LlamaPeripheryTestSetup, DeployLlamaRewardsClaimer { + // Sample ERC20 token IERC20 public constant USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); + // Mock rewards contracts + MockRewardsContract rewardsContract1; + MockRewardsContract rewardsContract2; + MockRewardsContract rewardsContract3; + MockRewardsContract rewardsContract4; + MockRewardsContract rewardsContract5; + function setUp() public virtual override { LlamaPeripheryTestSetup.setUp(); @@ -25,23 +37,23 @@ contract LlamaRewardsClaimTestSetup is LlamaPeripheryTestSetup, DeployLlamaRewar // Deploy the mock rewards contracts and set LlamaAccount as the reward claimer. // Deal ETH and UNI to the rewards contracts. - MockRewardsContract rewardsContract1 = new MockRewardsContract(address(ACCOUNT)); + rewardsContract1 = new MockRewardsContract(address(ACCOUNT)); vm.deal(address(rewardsContract1), 1 ether); deal(address(USDC), address(rewardsContract1), 1 ether); - MockRewardsContract rewardsContract2 = new MockRewardsContract(address(ACCOUNT)); + rewardsContract2 = new MockRewardsContract(address(ACCOUNT)); vm.deal(address(rewardsContract2), 1 ether); deal(address(USDC), address(rewardsContract2), 1 ether); - MockRewardsContract rewardsContract3 = new MockRewardsContract(address(ACCOUNT)); + rewardsContract3 = new MockRewardsContract(address(ACCOUNT)); vm.deal(address(rewardsContract3), 1 ether); deal(address(USDC), address(rewardsContract3), 1 ether); - MockRewardsContract rewardsContract4 = new MockRewardsContract(address(ACCOUNT)); + rewardsContract4 = new MockRewardsContract(address(ACCOUNT)); vm.deal(address(rewardsContract4), 1 ether); deal(address(USDC), address(rewardsContract4), 1 ether); - MockRewardsContract rewardsContract5 = new MockRewardsContract(address(ACCOUNT)); + rewardsContract5 = new MockRewardsContract(address(ACCOUNT)); vm.deal(address(rewardsContract5), 1 ether); deal(address(USDC), address(rewardsContract5), 1 ether); @@ -82,9 +94,61 @@ contract LlamaRewardsClaimTestSetup is LlamaPeripheryTestSetup, DeployLlamaRewar rewardsClaimStorage.setAuthorizedTargetSelectors(data); // Set the Llama rewards claim guard. - CORE.setGuard( - address(ACCOUNT), bytes4(keccak256("execute(address,bool,uint256,bytes)")), address(rewardsClaimGuard) + CORE.setGuard(address(ACCOUNT), ILlamaAccount.execute.selector, address(rewardsClaimGuard)); + + // Assign LlamaAccount.execute permission to Core Team role. + POLICY.setRolePermission( + CORE_TEAM_ROLE, + ILlamaPolicy.PermissionData(address(ACCOUNT), ILlamaAccount.execute.selector, address(STRATEGY)), + true ); vm.stopPrank(); } + + // ========================= + // ======== Helpers ======== + // ========================= + + function _createActionClaimRewards() public returns (ActionInfo memory actionInfo) { + LlamaRewardsClaimAccountExtension.TargetData[] memory targetData = + new LlamaRewardsClaimAccountExtension.TargetData[](10); + targetData[0] = LlamaRewardsClaimAccountExtension.TargetData( + address(rewardsContract1), 0, abi.encodeCall(MockRewardsContract.withdrawETH, ()) + ); + targetData[1] = LlamaRewardsClaimAccountExtension.TargetData( + address(rewardsContract1), 0, abi.encodeCall(MockRewardsContract.withdrawERC20, (USDC)) + ); + targetData[2] = LlamaRewardsClaimAccountExtension.TargetData( + address(rewardsContract2), 0, abi.encodeCall(MockRewardsContract.withdrawETH, ()) + ); + targetData[3] = LlamaRewardsClaimAccountExtension.TargetData( + address(rewardsContract2), 0, abi.encodeCall(MockRewardsContract.withdrawERC20, (USDC)) + ); + targetData[4] = LlamaRewardsClaimAccountExtension.TargetData( + address(rewardsContract3), 0, abi.encodeCall(MockRewardsContract.withdrawETH, ()) + ); + targetData[5] = LlamaRewardsClaimAccountExtension.TargetData( + address(rewardsContract3), 0, abi.encodeCall(MockRewardsContract.withdrawERC20, (USDC)) + ); + targetData[6] = LlamaRewardsClaimAccountExtension.TargetData( + address(rewardsContract4), 0, abi.encodeCall(MockRewardsContract.withdrawETH, ()) + ); + targetData[7] = LlamaRewardsClaimAccountExtension.TargetData( + address(rewardsContract4), 0, abi.encodeCall(MockRewardsContract.withdrawERC20, (USDC)) + ); + targetData[8] = LlamaRewardsClaimAccountExtension.TargetData( + address(rewardsContract5), 0, abi.encodeCall(MockRewardsContract.withdrawETH, ()) + ); + targetData[9] = LlamaRewardsClaimAccountExtension.TargetData( + address(rewardsContract5), 0, abi.encodeCall(MockRewardsContract.withdrawERC20, (USDC)) + ); + + bytes memory accountExtensionData = abi.encodeCall(LlamaRewardsClaimAccountExtension.claimRewards, (targetData)); + bytes memory data = + abi.encodeCall(ILlamaAccount.execute, (address(rewardsClaimAccountExtension), true, 0, accountExtensionData)); + + vm.prank(coreTeam1); + uint256 actionId = CORE.createAction(CORE_TEAM_ROLE, STRATEGY, address(ACCOUNT), 0, data, ""); + actionInfo = ActionInfo(actionId, coreTeam1, CORE_TEAM_ROLE, STRATEGY, address(ACCOUNT), 0, data); + } } From a4d92653ce14beef269abb99f6e7dc7735f2b343 Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Mon, 8 Apr 2024 13:05:48 -0400 Subject: [PATCH 23/40] test_RevertIf_SelectorIsNotClaimRewards --- .../LlamaRewardsClaimGuard.t.sol | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/test/rewards-claimer/LlamaRewardsClaimGuard.t.sol b/test/rewards-claimer/LlamaRewardsClaimGuard.t.sol index b080ff1..370aa45 100644 --- a/test/rewards-claimer/LlamaRewardsClaimGuard.t.sol +++ b/test/rewards-claimer/LlamaRewardsClaimGuard.t.sol @@ -23,7 +23,11 @@ contract Constructor is LlamaRewardsClaimGuardTest { } contract ValidateActionCreation is LlamaRewardsClaimGuardTest { - function _setupClaimRewardsData() public returns (LlamaRewardsClaimAccountExtension.TargetData[] memory targetData) { + function _setupClaimRewardsData() + public + view + returns (LlamaRewardsClaimAccountExtension.TargetData[] memory targetData) + { targetData = new LlamaRewardsClaimAccountExtension.TargetData[](10); targetData[0] = LlamaRewardsClaimAccountExtension.TargetData( address(rewardsContract1), 0, abi.encodeCall(MockRewardsContract.withdrawETH, ()) @@ -76,4 +80,22 @@ contract ValidateActionCreation is LlamaRewardsClaimGuardTest { ); CORE.createAction(CORE_TEAM_ROLE, STRATEGY, address(ACCOUNT), 0, data, ""); } + + function test_RevertIf_SelectorIsNotClaimRewards() public { + LlamaRewardsClaimAccountExtension.TargetData[] memory targetData = _setupClaimRewardsData(); + + bytes4 dummySelector = bytes4(0); + + bytes memory accountExtensionData = abi.encodeWithSelector(dummySelector, targetData); + bytes memory data = + abi.encodeCall(ILlamaAccount.execute, (address(rewardsClaimAccountExtension), true, 0, accountExtensionData)); + + vm.prank(coreTeam1); + vm.expectRevert( + abi.encodeWithSelector( + LlamaRewardsClaimGuard.UnauthorizedCall.selector, address(rewardsClaimAccountExtension), dummySelector, true + ) + ); + CORE.createAction(CORE_TEAM_ROLE, STRATEGY, address(ACCOUNT), 0, data, ""); + } } From cf87865716d12b77e74131f671eb62888b973225 Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Mon, 8 Apr 2024 13:07:33 -0400 Subject: [PATCH 24/40] test_RevertIf_NotDelegateCall --- .../LlamaRewardsClaimGuard.t.sol | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/rewards-claimer/LlamaRewardsClaimGuard.t.sol b/test/rewards-claimer/LlamaRewardsClaimGuard.t.sol index 370aa45..6bb925b 100644 --- a/test/rewards-claimer/LlamaRewardsClaimGuard.t.sol +++ b/test/rewards-claimer/LlamaRewardsClaimGuard.t.sol @@ -98,4 +98,23 @@ contract ValidateActionCreation is LlamaRewardsClaimGuardTest { ); CORE.createAction(CORE_TEAM_ROLE, STRATEGY, address(ACCOUNT), 0, data, ""); } + + function test_RevertIf_NotDelegateCall() public { + LlamaRewardsClaimAccountExtension.TargetData[] memory targetData = _setupClaimRewardsData(); + + bytes memory accountExtensionData = abi.encodeCall(LlamaRewardsClaimAccountExtension.claimRewards, (targetData)); + bytes memory data = + abi.encodeCall(ILlamaAccount.execute, (address(rewardsClaimAccountExtension), false, 0, accountExtensionData)); + + vm.prank(coreTeam1); + vm.expectRevert( + abi.encodeWithSelector( + LlamaRewardsClaimGuard.UnauthorizedCall.selector, + address(rewardsClaimAccountExtension), + LlamaRewardsClaimAccountExtension.claimRewards.selector, + false + ) + ); + CORE.createAction(CORE_TEAM_ROLE, STRATEGY, address(ACCOUNT), 0, data, ""); + } } From 58f0b3e4b644fd15ff73923a4ad9a6d5ef74189c Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Mon, 8 Apr 2024 13:23:34 -0400 Subject: [PATCH 25/40] test_PositiveFlow --- test/rewards-claimer/LlamaRewardsClaimGuard.t.sol | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/rewards-claimer/LlamaRewardsClaimGuard.t.sol b/test/rewards-claimer/LlamaRewardsClaimGuard.t.sol index 6bb925b..d3e7f75 100644 --- a/test/rewards-claimer/LlamaRewardsClaimGuard.t.sol +++ b/test/rewards-claimer/LlamaRewardsClaimGuard.t.sol @@ -61,6 +61,21 @@ contract ValidateActionCreation is LlamaRewardsClaimGuardTest { ); } + function test_PositiveFlow() public { + LlamaRewardsClaimAccountExtension.TargetData[] memory targetData = _setupClaimRewardsData(); + + bytes memory accountExtensionData = abi.encodeCall(LlamaRewardsClaimAccountExtension.claimRewards, (targetData)); + bytes memory data = + abi.encodeCall(ILlamaAccount.execute, (address(rewardsClaimAccountExtension), true, 0, accountExtensionData)); + + assertEq(CORE.actionsCount(), 12); + + vm.prank(coreTeam1); + CORE.createAction(CORE_TEAM_ROLE, STRATEGY, address(ACCOUNT), 0, data, ""); + + assertEq(CORE.actionsCount(), 13); + } + function test_RevertIf_TargetIsNotRewardsClaimer() public { LlamaRewardsClaimAccountExtension.TargetData[] memory targetData = _setupClaimRewardsData(); From fc602f767fb241b5e2a4d28a8579fa377b478691 Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Mon, 8 Apr 2024 14:29:47 -0400 Subject: [PATCH 26/40] refactor --- .../LlamaRewardsClaimGuard.t.sol | 38 ------------------- .../LlamaRewardsClaimTestSetup.sol | 15 ++++++-- 2 files changed, 12 insertions(+), 41 deletions(-) diff --git a/test/rewards-claimer/LlamaRewardsClaimGuard.t.sol b/test/rewards-claimer/LlamaRewardsClaimGuard.t.sol index d3e7f75..c5c917b 100644 --- a/test/rewards-claimer/LlamaRewardsClaimGuard.t.sol +++ b/test/rewards-claimer/LlamaRewardsClaimGuard.t.sol @@ -23,44 +23,6 @@ contract Constructor is LlamaRewardsClaimGuardTest { } contract ValidateActionCreation is LlamaRewardsClaimGuardTest { - function _setupClaimRewardsData() - public - view - returns (LlamaRewardsClaimAccountExtension.TargetData[] memory targetData) - { - targetData = new LlamaRewardsClaimAccountExtension.TargetData[](10); - targetData[0] = LlamaRewardsClaimAccountExtension.TargetData( - address(rewardsContract1), 0, abi.encodeCall(MockRewardsContract.withdrawETH, ()) - ); - targetData[1] = LlamaRewardsClaimAccountExtension.TargetData( - address(rewardsContract1), 0, abi.encodeCall(MockRewardsContract.withdrawERC20, (USDC)) - ); - targetData[2] = LlamaRewardsClaimAccountExtension.TargetData( - address(rewardsContract2), 0, abi.encodeCall(MockRewardsContract.withdrawETH, ()) - ); - targetData[3] = LlamaRewardsClaimAccountExtension.TargetData( - address(rewardsContract2), 0, abi.encodeCall(MockRewardsContract.withdrawERC20, (USDC)) - ); - targetData[4] = LlamaRewardsClaimAccountExtension.TargetData( - address(rewardsContract3), 0, abi.encodeCall(MockRewardsContract.withdrawETH, ()) - ); - targetData[5] = LlamaRewardsClaimAccountExtension.TargetData( - address(rewardsContract3), 0, abi.encodeCall(MockRewardsContract.withdrawERC20, (USDC)) - ); - targetData[6] = LlamaRewardsClaimAccountExtension.TargetData( - address(rewardsContract4), 0, abi.encodeCall(MockRewardsContract.withdrawETH, ()) - ); - targetData[7] = LlamaRewardsClaimAccountExtension.TargetData( - address(rewardsContract4), 0, abi.encodeCall(MockRewardsContract.withdrawERC20, (USDC)) - ); - targetData[8] = LlamaRewardsClaimAccountExtension.TargetData( - address(rewardsContract5), 0, abi.encodeCall(MockRewardsContract.withdrawETH, ()) - ); - targetData[9] = LlamaRewardsClaimAccountExtension.TargetData( - address(rewardsContract5), 0, abi.encodeCall(MockRewardsContract.withdrawERC20, (USDC)) - ); - } - function test_PositiveFlow() public { LlamaRewardsClaimAccountExtension.TargetData[] memory targetData = _setupClaimRewardsData(); diff --git a/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol b/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol index 7492583..2b46739 100644 --- a/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol +++ b/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol @@ -109,9 +109,12 @@ contract LlamaRewardsClaimTestSetup is LlamaPeripheryTestSetup, DeployLlamaRewar // ======== Helpers ======== // ========================= - function _createActionClaimRewards() public returns (ActionInfo memory actionInfo) { - LlamaRewardsClaimAccountExtension.TargetData[] memory targetData = - new LlamaRewardsClaimAccountExtension.TargetData[](10); + function _setupClaimRewardsData() + public + view + returns (LlamaRewardsClaimAccountExtension.TargetData[] memory targetData) + { + targetData = new LlamaRewardsClaimAccountExtension.TargetData[](10); targetData[0] = LlamaRewardsClaimAccountExtension.TargetData( address(rewardsContract1), 0, abi.encodeCall(MockRewardsContract.withdrawETH, ()) ); @@ -142,7 +145,10 @@ contract LlamaRewardsClaimTestSetup is LlamaPeripheryTestSetup, DeployLlamaRewar targetData[9] = LlamaRewardsClaimAccountExtension.TargetData( address(rewardsContract5), 0, abi.encodeCall(MockRewardsContract.withdrawERC20, (USDC)) ); + } + function _createActionClaimRewards() public returns (ActionInfo memory actionInfo) { + LlamaRewardsClaimAccountExtension.TargetData[] memory targetData = _setupClaimRewardsData(); bytes memory accountExtensionData = abi.encodeCall(LlamaRewardsClaimAccountExtension.claimRewards, (targetData)); bytes memory data = abi.encodeCall(ILlamaAccount.execute, (address(rewardsClaimAccountExtension), true, 0, accountExtensionData)); @@ -150,5 +156,8 @@ contract LlamaRewardsClaimTestSetup is LlamaPeripheryTestSetup, DeployLlamaRewar vm.prank(coreTeam1); uint256 actionId = CORE.createAction(CORE_TEAM_ROLE, STRATEGY, address(ACCOUNT), 0, data, ""); actionInfo = ActionInfo(actionId, coreTeam1, CORE_TEAM_ROLE, STRATEGY, address(ACCOUNT), 0, data); + + vm.prank(coreTeam1); + CORE.castApproval(CORE_TEAM_ROLE, actionInfo, ""); } } From ff5c037b6504c7fc7da5748fb37e501c6cd85c8d Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Mon, 8 Apr 2024 14:32:38 -0400 Subject: [PATCH 27/40] rewardsclaimaccountextension --- .../LlamaRewardsClaimAccountExtension.t.sol | 25 +++++++++++++++++++ .../LlamaRewardsClaimGuard.t.sol | 1 - 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 test/rewards-claimer/LlamaRewardsClaimAccountExtension.t.sol diff --git a/test/rewards-claimer/LlamaRewardsClaimAccountExtension.t.sol b/test/rewards-claimer/LlamaRewardsClaimAccountExtension.t.sol new file mode 100644 index 0000000..5a24265 --- /dev/null +++ b/test/rewards-claimer/LlamaRewardsClaimAccountExtension.t.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import {Test, console2} from "forge-std/Test.sol"; + +import {MockRewardsContract} from "test/mock/MockRewardsContract.sol"; +import {LlamaRewardsClaimTestSetup} from "test/rewards-claimer/LlamaRewardsClaimTestSetup.sol"; + +import {ILlamaAccount} from "src/interfaces/ILlamaAccount.sol"; +import {LlamaRewardsClaimAccountExtension} from "src/rewards-claimer/LlamaRewardsClaimAccountExtension.sol"; +import {LlamaRewardsClaimGuard} from "src/rewards-claimer/LlamaRewardsClaimGuard.sol"; + +contract LlamaRewardsClaimAccountExtensionTest is LlamaRewardsClaimTestSetup { + function setUp() public override { + LlamaRewardsClaimTestSetup.setUp(); + } +} + +contract Constructor is LlamaRewardsClaimAccountExtensionTest { + function test_SetsRewardsClaimStorage() public { + assertEq(address(rewardsClaimAccountExtension.REWARDS_CLAIM_STORAGE()), address(rewardsClaimStorage)); + } +} + +contract ClaimRewards is LlamaRewardsClaimAccountExtensionTest {} diff --git a/test/rewards-claimer/LlamaRewardsClaimGuard.t.sol b/test/rewards-claimer/LlamaRewardsClaimGuard.t.sol index c5c917b..05a3bc1 100644 --- a/test/rewards-claimer/LlamaRewardsClaimGuard.t.sol +++ b/test/rewards-claimer/LlamaRewardsClaimGuard.t.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.23; import {Test, console2} from "forge-std/Test.sol"; -import {MockRewardsContract} from "test/mock/MockRewardsContract.sol"; import {LlamaRewardsClaimTestSetup} from "test/rewards-claimer/LlamaRewardsClaimTestSetup.sol"; import {ILlamaAccount} from "src/interfaces/ILlamaAccount.sol"; From 4e300274cd28f79f0d61088be5b84f65ffcc6c3a Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Mon, 8 Apr 2024 19:33:21 -0400 Subject: [PATCH 28/40] test_ClaimRewards --- .../LlamaRewardsClaimAccountExtension.sol | 2 +- test/mock/MockRewardsContract.sol | 5 ++ .../LlamaRewardsClaimAccountExtension.t.sol | 84 ++++++++++++++++++- 3 files changed, 89 insertions(+), 2 deletions(-) diff --git a/src/rewards-claimer/LlamaRewardsClaimAccountExtension.sol b/src/rewards-claimer/LlamaRewardsClaimAccountExtension.sol index 71dfbb7..e767c3d 100644 --- a/src/rewards-claimer/LlamaRewardsClaimAccountExtension.sol +++ b/src/rewards-claimer/LlamaRewardsClaimAccountExtension.sol @@ -39,7 +39,7 @@ contract LlamaRewardsClaimAccountExtension is LlamaBaseAccountExtension { function claimRewards(TargetData[] memory targetData) external onlyDelegateCall returns (bytes[] memory returnData) { uint256 length = targetData.length; returnData = new bytes[](length); - for (uint256 i = 0; i < length; LlamaUtils.uncheckedIncrement(i)) { + for (uint256 i = 0; i < length; i = LlamaUtils.uncheckedIncrement(i)) { address target = targetData[i].target; uint256 value = targetData[i].value; bytes memory callData = targetData[i].data; diff --git a/test/mock/MockRewardsContract.sol b/test/mock/MockRewardsContract.sol index 01823c8..e170e2c 100644 --- a/test/mock/MockRewardsContract.sol +++ b/test/mock/MockRewardsContract.sol @@ -15,6 +15,9 @@ contract MockRewardsContract { _; } + event ethWithdrawn(address indexed from, address indexed to, uint256 amount); + event erc20Withdrawn(IERC20 indexed token, address indexed from, address indexed to, uint256 amount); + address public immutable REWARD_CLAIMER; constructor(address rewardClaimer) { @@ -22,10 +25,12 @@ contract MockRewardsContract { } function withdrawETH() external payable onlyRewardClaimer { + emit ethWithdrawn(address(this), REWARD_CLAIMER, address(this).balance); Address.sendValue(payable(REWARD_CLAIMER), address(this).balance); } function withdrawERC20(IERC20 token) external onlyRewardClaimer { + emit erc20Withdrawn(token, address(this), REWARD_CLAIMER, token.balanceOf(address(this))); token.safeTransfer(REWARD_CLAIMER, token.balanceOf(address(this))); } } diff --git a/test/rewards-claimer/LlamaRewardsClaimAccountExtension.t.sol b/test/rewards-claimer/LlamaRewardsClaimAccountExtension.t.sol index 5a24265..4312d7c 100644 --- a/test/rewards-claimer/LlamaRewardsClaimAccountExtension.t.sol +++ b/test/rewards-claimer/LlamaRewardsClaimAccountExtension.t.sol @@ -3,14 +3,20 @@ pragma solidity ^0.8.23; import {Test, console2} from "forge-std/Test.sol"; +import {IERC20} from "@openzeppelin/token/ERC20/IERC20.sol"; + import {MockRewardsContract} from "test/mock/MockRewardsContract.sol"; import {LlamaRewardsClaimTestSetup} from "test/rewards-claimer/LlamaRewardsClaimTestSetup.sol"; import {ILlamaAccount} from "src/interfaces/ILlamaAccount.sol"; +import {ActionInfo} from "src/lib/Structs.sol"; import {LlamaRewardsClaimAccountExtension} from "src/rewards-claimer/LlamaRewardsClaimAccountExtension.sol"; import {LlamaRewardsClaimGuard} from "src/rewards-claimer/LlamaRewardsClaimGuard.sol"; contract LlamaRewardsClaimAccountExtensionTest is LlamaRewardsClaimTestSetup { + event ethWithdrawn(address indexed from, address indexed to, uint256 amount); + event erc20Withdrawn(IERC20 indexed token, address indexed from, address indexed to, uint256 amount); + function setUp() public override { LlamaRewardsClaimTestSetup.setUp(); } @@ -22,4 +28,80 @@ contract Constructor is LlamaRewardsClaimAccountExtensionTest { } } -contract ClaimRewards is LlamaRewardsClaimAccountExtensionTest {} +contract ClaimRewards is LlamaRewardsClaimAccountExtensionTest { + function _createAndQueueActionClaimRewards() public returns (ActionInfo memory actionInfo) { + LlamaRewardsClaimAccountExtension.TargetData[] memory targetData = _setupClaimRewardsData(); + bytes memory accountExtensionData = abi.encodeCall(LlamaRewardsClaimAccountExtension.claimRewards, (targetData)); + bytes memory data = + abi.encodeCall(ILlamaAccount.execute, (address(rewardsClaimAccountExtension), true, 0, accountExtensionData)); + + // Create an action to claim rewards. + vm.prank(coreTeam1); + uint256 actionId = CORE.createAction(CORE_TEAM_ROLE, STRATEGY, address(ACCOUNT), 0, data, ""); + actionInfo = ActionInfo(actionId, coreTeam1, CORE_TEAM_ROLE, STRATEGY, address(ACCOUNT), 0, data); + + // Approve and queue the action. + vm.prank(coreTeam1); + CORE.castApproval(CORE_TEAM_ROLE, actionInfo, ""); + vm.prank(coreTeam2); + CORE.castApproval(CORE_TEAM_ROLE, actionInfo, ""); + vm.prank(coreTeam3); + CORE.castApproval(CORE_TEAM_ROLE, actionInfo, ""); + } + + function test_ClaimRewards() public { + ActionInfo memory actionInfo = _createAndQueueActionClaimRewards(); + + assertEq(address(rewardsContract1).balance, 1 ether); + assertEq(USDC.balanceOf(address(rewardsContract1)), 1 ether); + assertEq(address(rewardsContract2).balance, 1 ether); + assertEq(USDC.balanceOf(address(rewardsContract2)), 1 ether); + assertEq(address(rewardsContract3).balance, 1 ether); + assertEq(USDC.balanceOf(address(rewardsContract3)), 1 ether); + assertEq(address(rewardsContract4).balance, 1 ether); + assertEq(USDC.balanceOf(address(rewardsContract4)), 1 ether); + assertEq(address(rewardsContract5).balance, 1 ether); + assertEq(USDC.balanceOf(address(rewardsContract5)), 1 ether); + + uint256 initialLlamaAccountETHBalance = address(ACCOUNT).balance; + uint256 initialLlamaAccountUSDCBalance = USDC.balanceOf(address(ACCOUNT)); + + // Execute the action. + vm.expectEmit(); + emit ethWithdrawn(address(rewardsContract1), address(ACCOUNT), 1 ether); + vm.expectEmit(); + emit erc20Withdrawn(USDC, address(rewardsContract1), address(ACCOUNT), 1 ether); + vm.expectEmit(); + emit ethWithdrawn(address(rewardsContract2), address(ACCOUNT), 1 ether); + vm.expectEmit(); + emit erc20Withdrawn(USDC, address(rewardsContract2), address(ACCOUNT), 1 ether); + vm.expectEmit(); + emit ethWithdrawn(address(rewardsContract3), address(ACCOUNT), 1 ether); + vm.expectEmit(); + emit erc20Withdrawn(USDC, address(rewardsContract3), address(ACCOUNT), 1 ether); + vm.expectEmit(); + emit ethWithdrawn(address(rewardsContract4), address(ACCOUNT), 1 ether); + vm.expectEmit(); + emit erc20Withdrawn(USDC, address(rewardsContract4), address(ACCOUNT), 1 ether); + vm.expectEmit(); + emit ethWithdrawn(address(rewardsContract5), address(ACCOUNT), 1 ether); + vm.expectEmit(); + emit erc20Withdrawn(USDC, address(rewardsContract5), address(ACCOUNT), 1 ether); + + CORE.executeAction(actionInfo); + + assertEq(address(rewardsContract1).balance, 0); + assertEq(USDC.balanceOf(address(rewardsContract1)), 0); + assertEq(address(rewardsContract2).balance, 0); + assertEq(USDC.balanceOf(address(rewardsContract2)), 0); + assertEq(address(rewardsContract3).balance, 0); + assertEq(USDC.balanceOf(address(rewardsContract3)), 0); + assertEq(address(rewardsContract4).balance, 0); + assertEq(USDC.balanceOf(address(rewardsContract4)), 0); + assertEq(address(rewardsContract5).balance, 0); + assertEq(USDC.balanceOf(address(rewardsContract5)), 0); + + assertEq(address(ACCOUNT).balance, initialLlamaAccountETHBalance + 5 ether); + assertEq(USDC.balanceOf(address(ACCOUNT)), initialLlamaAccountUSDCBalance + 5 ether); + } +} From 8731baedbebe698f6ef214d03f4a83e6708c7c3e Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Mon, 8 Apr 2024 20:07:38 -0400 Subject: [PATCH 29/40] test_RevertIf_NotAuthorizedTargetSelector --- src/interfaces/ILlamaAccount.sol | 4 +++ src/interfaces/ILlamaCore.sol | 4 +++ .../LlamaRewardsClaimAccountExtension.t.sol | 29 +++++++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/src/interfaces/ILlamaAccount.sol b/src/interfaces/ILlamaAccount.sol index 75ff643..57d47ca 100644 --- a/src/interfaces/ILlamaAccount.sol +++ b/src/interfaces/ILlamaAccount.sol @@ -5,6 +5,10 @@ pragma solidity ^0.8.23; /// @author Llama (devsdosomething@llama.xyz) /// @notice This is the interface for Llama accounts which can be used to hold assets for a Llama instance. interface ILlamaAccount { + /// @dev External call failed. + /// @param result Data returned by the called function. + error FailedExecution(bytes result); + // -------- For Inspection -------- /// @notice Returns the address of the Llama instance's executor. diff --git a/src/interfaces/ILlamaCore.sol b/src/interfaces/ILlamaCore.sol index c83dda5..4e22981 100644 --- a/src/interfaces/ILlamaCore.sol +++ b/src/interfaces/ILlamaCore.sol @@ -29,6 +29,10 @@ interface ILlamaCore { /// @param current The current state of the action. error InvalidActionState(ActionState current); + /// @dev Action execution failed. + /// @param reason Data returned by the function called by the action. + error FailedActionExecution(bytes reason); + function actionGuard(address target, bytes4 selector) external view returns (address guard); function actionsCount() external view returns (uint256); diff --git a/test/rewards-claimer/LlamaRewardsClaimAccountExtension.t.sol b/test/rewards-claimer/LlamaRewardsClaimAccountExtension.t.sol index 4312d7c..9d8cd48 100644 --- a/test/rewards-claimer/LlamaRewardsClaimAccountExtension.t.sol +++ b/test/rewards-claimer/LlamaRewardsClaimAccountExtension.t.sol @@ -9,9 +9,11 @@ import {MockRewardsContract} from "test/mock/MockRewardsContract.sol"; import {LlamaRewardsClaimTestSetup} from "test/rewards-claimer/LlamaRewardsClaimTestSetup.sol"; import {ILlamaAccount} from "src/interfaces/ILlamaAccount.sol"; +import {ILlamaCore} from "src/interfaces/ILlamaCore.sol"; import {ActionInfo} from "src/lib/Structs.sol"; import {LlamaRewardsClaimAccountExtension} from "src/rewards-claimer/LlamaRewardsClaimAccountExtension.sol"; import {LlamaRewardsClaimGuard} from "src/rewards-claimer/LlamaRewardsClaimGuard.sol"; +import {LlamaRewardsClaimStorage} from "src/rewards-claimer/LlamaRewardsClaimStorage.sol"; contract LlamaRewardsClaimAccountExtensionTest is LlamaRewardsClaimTestSetup { event ethWithdrawn(address indexed from, address indexed to, uint256 amount); @@ -104,4 +106,31 @@ contract ClaimRewards is LlamaRewardsClaimAccountExtensionTest { assertEq(address(ACCOUNT).balance, initialLlamaAccountETHBalance + 5 ether); assertEq(USDC.balanceOf(address(ACCOUNT)), initialLlamaAccountUSDCBalance + 5 ether); } + + function test_RevertIf_NotAuthorizedTargetSelector() public { + ActionInfo memory actionInfo = _createAndQueueActionClaimRewards(); + + // Unauthorize target selector. + LlamaRewardsClaimStorage.TargetSelectorAuthorization[] memory data = + new LlamaRewardsClaimStorage.TargetSelectorAuthorization[](1); + data[0] = LlamaRewardsClaimStorage.TargetSelectorAuthorization( + address(rewardsContract1), MockRewardsContract.withdrawETH.selector, false + ); + vm.prank(address(EXECUTOR)); + rewardsClaimStorage.setAuthorizedTargetSelectors(data); + + bytes memory expectedErr = abi.encodeWithSelector( + ILlamaCore.FailedActionExecution.selector, + abi.encodeWithSelector( + ILlamaAccount.FailedExecution.selector, + abi.encodeWithSelector( + LlamaRewardsClaimAccountExtension.UnauthorizedTargetSelector.selector, + address(rewardsContract1), + MockRewardsContract.withdrawETH.selector + ) + ) + ); + vm.expectRevert(expectedErr); + CORE.executeAction(actionInfo); + } } From fedf515b7a4787b1074756cac720a17fd24e3100 Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Mon, 8 Apr 2024 20:11:18 -0400 Subject: [PATCH 30/40] cleanup --- .../LlamaRewardsClaimTestSetup.sol | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol b/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol index 2b46739..865ad14 100644 --- a/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol +++ b/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol @@ -14,7 +14,6 @@ import {DeployLlamaRewardsClaimer} from "script/DeployLlamaRewardsClaimer.s.sol" import {ILlamaAccount} from "src/interfaces/ILlamaAccount.sol"; import {ILlamaPolicy} from "src/interfaces/ILlamaPolicy.sol"; -import {ActionInfo} from "src/lib/Structs.sol"; import {LlamaRewardsClaimAccountExtension} from "src/rewards-claimer/LlamaRewardsClaimAccountExtension.sol"; import {LlamaRewardsClaimStorage} from "src/rewards-claimer/LlamaRewardsClaimStorage.sol"; @@ -146,18 +145,4 @@ contract LlamaRewardsClaimTestSetup is LlamaPeripheryTestSetup, DeployLlamaRewar address(rewardsContract5), 0, abi.encodeCall(MockRewardsContract.withdrawERC20, (USDC)) ); } - - function _createActionClaimRewards() public returns (ActionInfo memory actionInfo) { - LlamaRewardsClaimAccountExtension.TargetData[] memory targetData = _setupClaimRewardsData(); - bytes memory accountExtensionData = abi.encodeCall(LlamaRewardsClaimAccountExtension.claimRewards, (targetData)); - bytes memory data = - abi.encodeCall(ILlamaAccount.execute, (address(rewardsClaimAccountExtension), true, 0, accountExtensionData)); - - vm.prank(coreTeam1); - uint256 actionId = CORE.createAction(CORE_TEAM_ROLE, STRATEGY, address(ACCOUNT), 0, data, ""); - actionInfo = ActionInfo(actionId, coreTeam1, CORE_TEAM_ROLE, STRATEGY, address(ACCOUNT), 0, data); - - vm.prank(coreTeam1); - CORE.castApproval(CORE_TEAM_ROLE, actionInfo, ""); - } } From 66b34bdf82c4819bf55dec97f18c8d48a42c670f Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Tue, 9 Apr 2024 18:02:39 -0400 Subject: [PATCH 31/40] receive payable --- test/mock/MockRewardsContract.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/mock/MockRewardsContract.sol b/test/mock/MockRewardsContract.sol index e170e2c..819365e 100644 --- a/test/mock/MockRewardsContract.sol +++ b/test/mock/MockRewardsContract.sol @@ -24,6 +24,8 @@ contract MockRewardsContract { REWARD_CLAIMER = rewardClaimer; } + receive() external payable {} + function withdrawETH() external payable onlyRewardClaimer { emit ethWithdrawn(address(this), REWARD_CLAIMER, address(this).balance); Address.sendValue(payable(REWARD_CLAIMER), address(this).balance); From f10f46819a2f45b37117d679a49d38e2e2b6feab Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Tue, 9 Apr 2024 19:03:20 -0400 Subject: [PATCH 32/40] test_RevertIf_NotDelegateCalled --- .../LlamaRewardsClaimAccountExtension.t.sol | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/rewards-claimer/LlamaRewardsClaimAccountExtension.t.sol b/test/rewards-claimer/LlamaRewardsClaimAccountExtension.t.sol index 9d8cd48..a37c371 100644 --- a/test/rewards-claimer/LlamaRewardsClaimAccountExtension.t.sol +++ b/test/rewards-claimer/LlamaRewardsClaimAccountExtension.t.sol @@ -14,6 +14,7 @@ import {ActionInfo} from "src/lib/Structs.sol"; import {LlamaRewardsClaimAccountExtension} from "src/rewards-claimer/LlamaRewardsClaimAccountExtension.sol"; import {LlamaRewardsClaimGuard} from "src/rewards-claimer/LlamaRewardsClaimGuard.sol"; import {LlamaRewardsClaimStorage} from "src/rewards-claimer/LlamaRewardsClaimStorage.sol"; +import {LlamaBaseAccountExtension} from "src/common/LlamaBaseAccountExtension.sol"; contract LlamaRewardsClaimAccountExtensionTest is LlamaRewardsClaimTestSetup { event ethWithdrawn(address indexed from, address indexed to, uint256 amount); @@ -133,4 +134,10 @@ contract ClaimRewards is LlamaRewardsClaimAccountExtensionTest { vm.expectRevert(expectedErr); CORE.executeAction(actionInfo); } + + function test_RevertIf_NotDelegateCalled() public { + LlamaRewardsClaimAccountExtension.TargetData[] memory targetData = _setupClaimRewardsData(); + vm.expectRevert(LlamaBaseAccountExtension.OnlyDelegateCall.selector); + rewardsClaimAccountExtension.claimRewards(targetData); + } } From bdc4ed8072280902d3a6440d6ed526e9aa8f239f Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Thu, 11 Apr 2024 18:41:40 -0400 Subject: [PATCH 33/40] account multicall --- justfile | 6 +- script/DeployLlamaAccountMulticall.s.sol | 70 ++++++++++++++ script/DeployLlamaRewardsClaimer.s.sol | 70 -------------- ...g.json => mockAccountMulticallConfig.json} | 2 +- ...onfig.json => accountMulticallConfig.json} | 2 +- .../LlamaAccountMulticallExtension.sol} | 26 ++--- .../LlamaAccountMulticallGuard.sol} | 23 ++--- .../LlamaAccountMulticallStorage.sol} | 8 +- .../LlamaAccountMulticallExtension.t.sol} | 41 ++++---- .../LlamaAccountMulticallGuard.t.sol | 96 +++++++++++++++++++ .../LlamaAccountMulticallStorage.t.sol | 56 +++++++++++ .../LlamaAccountMulticallTestSetup.sol} | 70 +++++++------- .../LlamaRewardsClaimGuard.t.sol | 96 ------------------- .../LlamaRewardsClaimStorage.t.sol | 54 ----------- 14 files changed, 309 insertions(+), 311 deletions(-) create mode 100644 script/DeployLlamaAccountMulticall.s.sol delete mode 100644 script/DeployLlamaRewardsClaimer.s.sol rename script/input/1/{mockRewardsClaimerConfig.json => mockAccountMulticallConfig.json} (78%) rename script/input/11155111/{rewardsClaimerConfig.json => accountMulticallConfig.json} (77%) rename src/{rewards-claimer/LlamaRewardsClaimAccountExtension.sol => account-multicall/LlamaAccountMulticallExtension.sol} (62%) rename src/{rewards-claimer/LlamaRewardsClaimGuard.sol => account-multicall/LlamaAccountMulticallGuard.sol} (57%) rename src/{rewards-claimer/LlamaRewardsClaimStorage.sol => account-multicall/LlamaAccountMulticallStorage.sol} (89%) rename test/{rewards-claimer/LlamaRewardsClaimAccountExtension.t.sol => account-multicall/LlamaAccountMulticallExtension.t.sol} (74%) create mode 100644 test/account-multicall/LlamaAccountMulticallGuard.t.sol create mode 100644 test/account-multicall/LlamaAccountMulticallStorage.t.sol rename test/{rewards-claimer/LlamaRewardsClaimTestSetup.sol => account-multicall/LlamaAccountMulticallTestSetup.sol} (65%) delete mode 100644 test/rewards-claimer/LlamaRewardsClaimGuard.t.sol delete mode 100644 test/rewards-claimer/LlamaRewardsClaimStorage.t.sol diff --git a/justfile b/justfile index 9787e0c..f68eb32 100644 --- a/justfile +++ b/justfile @@ -28,10 +28,10 @@ dry-run-deploy-voting-module: (run-deploy-voting-module-script '') deploy-voting-module: (run-deploy-voting-module-script '--broadcast --verify') -run-deploy-rewards-claimer-script flags: (run-script 'DeployLlamaRewardsClaimer' flags '--sig "run(string)"' '"rewardsClaimerConfig.json"') +run-deploy-account-multicall-script flags: (run-script 'DeployLlamaAccountMulticall' flags '--sig "run(string)"' '"accountMulticallConfig.json"') -dry-run-deploy-rewards-claimer: (run-deploy-rewards-claimer-script '') +dry-run-deploy-account-multicall: (run-deploy-account-multicall-script '') -deploy-rewards-claimer: (run-deploy-rewards-claimer-script '--broadcast --verify --slow') +deploy-account-multicall: (run-deploy-account-multicall-script '--broadcast --verify --slow') verify: (run-script 'DeployLlamaTokenVotingFactory' '--verify --resume') diff --git a/script/DeployLlamaAccountMulticall.s.sol b/script/DeployLlamaAccountMulticall.s.sol new file mode 100644 index 0000000..a8499a5 --- /dev/null +++ b/script/DeployLlamaAccountMulticall.s.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +import {Script, stdJson} from "forge-std/Script.sol"; + +import {DeployUtils} from "script/DeployUtils.sol"; + +import {LlamaAccountMulticallExtension} from "src/account-multicall/LlamaAccountMulticallExtension.sol"; +import {LlamaAccountMulticallGuard} from "src/account-multicall/LlamaAccountMulticallGuard.sol"; +import {LlamaAccountMulticallStorage} from "src/account-multicall/LlamaAccountMulticallStorage.sol"; + +contract DeployLlamaAccountMulticall is Script { + using stdJson for string; + + struct TargetSelectorAuthorizationInputs { + // Attributes need to be in alphabetical order so JSON decodes properly. + string comment; + bytes selector; + address target; + } + + // Account multicall contracts. + LlamaAccountMulticallStorage accountMulticallStorage; + LlamaAccountMulticallExtension accountMulticallExtension; + LlamaAccountMulticallGuard accountMulticallGuard; + + function run(string memory configFile) public { + string memory jsonInput = DeployUtils.readScriptInput(configFile); + + address llamaExecutor = jsonInput.readAddress(".llamaExecutor"); + LlamaAccountMulticallStorage.TargetSelectorAuthorization[] memory data = readTargetSelectorAuthorizations(jsonInput); + + DeployUtils.print(string.concat("Deploying Llama rewards claimer contracts to chain:", vm.toString(block.chainid))); + + vm.broadcast(); + accountMulticallStorage = new LlamaAccountMulticallStorage(llamaExecutor, data); + DeployUtils.print(string.concat(" LlamaAccountMulticallStorage: ", vm.toString(address(accountMulticallStorage)))); + + vm.broadcast(); + accountMulticallExtension = new LlamaAccountMulticallExtension(accountMulticallStorage); + DeployUtils.print( + string.concat(" LlamaAccountMulticallExtension: ", vm.toString(address(accountMulticallExtension))) + ); + + vm.broadcast(); + accountMulticallGuard = new LlamaAccountMulticallGuard(accountMulticallExtension); + DeployUtils.print(string.concat(" LlamaAccountMulticallGuard: ", vm.toString(address(accountMulticallGuard)))); + } + + function readTargetSelectorAuthorizations(string memory jsonInput) + internal + pure + returns (LlamaAccountMulticallStorage.TargetSelectorAuthorization[] memory) + { + bytes memory data = jsonInput.parseRaw(".initialTargetSelectorAuthorizations"); + + TargetSelectorAuthorizationInputs[] memory rawConfigs = abi.decode(data, (TargetSelectorAuthorizationInputs[])); + + LlamaAccountMulticallStorage.TargetSelectorAuthorization[] memory configs = + new LlamaAccountMulticallStorage.TargetSelectorAuthorization[](rawConfigs.length); + + for (uint256 i = 0; i < rawConfigs.length; i++) { + configs[i].target = rawConfigs[i].target; + configs[i].selector = bytes4(rawConfigs[i].selector); + configs[i].isAuthorized = true; + } + + return configs; + } +} diff --git a/script/DeployLlamaRewardsClaimer.s.sol b/script/DeployLlamaRewardsClaimer.s.sol deleted file mode 100644 index d3ea264..0000000 --- a/script/DeployLlamaRewardsClaimer.s.sol +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.23; - -import {Script, stdJson} from "forge-std/Script.sol"; - -import {DeployUtils} from "script/DeployUtils.sol"; - -import {LlamaRewardsClaimAccountExtension} from "src/rewards-claimer/LlamaRewardsClaimAccountExtension.sol"; -import {LlamaRewardsClaimGuard} from "src/rewards-claimer/LlamaRewardsClaimGuard.sol"; -import {LlamaRewardsClaimStorage} from "src/rewards-claimer/LlamaRewardsClaimStorage.sol"; - -contract DeployLlamaRewardsClaimer is Script { - using stdJson for string; - - struct TargetSelectorAuthorizationInputs { - // Attributes need to be in alphabetical order so JSON decodes properly. - string comment; - bytes selector; - address target; - } - - // Rewards claim contracts. - LlamaRewardsClaimStorage rewardsClaimStorage; - LlamaRewardsClaimAccountExtension rewardsClaimAccountExtension; - LlamaRewardsClaimGuard rewardsClaimGuard; - - function run(string memory configFile) public { - string memory jsonInput = DeployUtils.readScriptInput(configFile); - - address llamaExecutor = jsonInput.readAddress(".llamaExecutor"); - LlamaRewardsClaimStorage.TargetSelectorAuthorization[] memory data = readTargetSelectorAuthorizations(jsonInput); - - DeployUtils.print(string.concat("Deploying Llama rewards claimer contracts to chain:", vm.toString(block.chainid))); - - vm.broadcast(); - rewardsClaimStorage = new LlamaRewardsClaimStorage(llamaExecutor, data); - DeployUtils.print(string.concat(" LlamaRewardsClaimStorage: ", vm.toString(address(rewardsClaimStorage)))); - - vm.broadcast(); - rewardsClaimAccountExtension = new LlamaRewardsClaimAccountExtension(rewardsClaimStorage); - DeployUtils.print( - string.concat(" LlamaRewardsClaimAccountExtension: ", vm.toString(address(rewardsClaimAccountExtension))) - ); - - vm.broadcast(); - rewardsClaimGuard = new LlamaRewardsClaimGuard(rewardsClaimAccountExtension); - DeployUtils.print(string.concat(" LlamaRewardsClaimGuard: ", vm.toString(address(rewardsClaimGuard)))); - } - - function readTargetSelectorAuthorizations(string memory jsonInput) - internal - pure - returns (LlamaRewardsClaimStorage.TargetSelectorAuthorization[] memory) - { - bytes memory data = jsonInput.parseRaw(".initialTargetSelectorAuthorizations"); - - TargetSelectorAuthorizationInputs[] memory rawConfigs = abi.decode(data, (TargetSelectorAuthorizationInputs[])); - - LlamaRewardsClaimStorage.TargetSelectorAuthorization[] memory configs = - new LlamaRewardsClaimStorage.TargetSelectorAuthorization[](rawConfigs.length); - - for (uint256 i = 0; i < rawConfigs.length; i++) { - configs[i].target = rawConfigs[i].target; - configs[i].selector = bytes4(rawConfigs[i].selector); - configs[i].isAuthorized = true; - } - - return configs; - } -} diff --git a/script/input/1/mockRewardsClaimerConfig.json b/script/input/1/mockAccountMulticallConfig.json similarity index 78% rename from script/input/1/mockRewardsClaimerConfig.json rename to script/input/1/mockAccountMulticallConfig.json index 3510945..89d934e 100644 --- a/script/input/1/mockRewardsClaimerConfig.json +++ b/script/input/1/mockAccountMulticallConfig.json @@ -1,5 +1,5 @@ { - "comment": "This is a rewards claimer deployment for Forge tests.", + "comment": "This is an account multicall deployment for Forge tests.", "llamaExecutor": "0xdAf00E9786cABB195a8a1Cf102730863aE94Dd75", "initialTargetSelectorAuthorizations": [ { diff --git a/script/input/11155111/rewardsClaimerConfig.json b/script/input/11155111/accountMulticallConfig.json similarity index 77% rename from script/input/11155111/rewardsClaimerConfig.json rename to script/input/11155111/accountMulticallConfig.json index 90efb70..c02bfcc 100644 --- a/script/input/11155111/rewardsClaimerConfig.json +++ b/script/input/11155111/accountMulticallConfig.json @@ -1,5 +1,5 @@ { - "comment": "This is an example rewards claimer deployment on Sepolia.", + "comment": "This is an example account multicall deployment on Sepolia.", "llamaExecutor": "0x0000000000000000000000000000000000000000", "initialTargetSelectorAuthorizations": [ { diff --git a/src/rewards-claimer/LlamaRewardsClaimAccountExtension.sol b/src/account-multicall/LlamaAccountMulticallExtension.sol similarity index 62% rename from src/rewards-claimer/LlamaRewardsClaimAccountExtension.sol rename to src/account-multicall/LlamaAccountMulticallExtension.sol index e767c3d..350095c 100644 --- a/src/rewards-claimer/LlamaRewardsClaimAccountExtension.sol +++ b/src/account-multicall/LlamaAccountMulticallExtension.sol @@ -1,15 +1,15 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.23; +import {LlamaAccountMulticallStorage} from "src/account-multicall/LlamaAccountMulticallStorage.sol"; import {LlamaBaseAccountExtension} from "src/common/LlamaBaseAccountExtension.sol"; import {LlamaUtils} from "src/lib/LlamaUtils.sol"; -import {LlamaRewardsClaimStorage} from "src/rewards-claimer/LlamaRewardsClaimStorage.sol"; -/// @title Llama Rewards Claim Account Extension +/// @title Llama Account Multicall Extension /// @author Llama (devsdosomething@llama.xyz) -/// @notice An account extension that claims rewards on behalf of the Llama account. +/// @notice An account extension that can multicall on behalf of the Llama account. /// @dev This contract should be delegate-called from a Llama account. -contract LlamaRewardsClaimAccountExtension is LlamaBaseAccountExtension { +contract LlamaAccountMulticallExtension is LlamaBaseAccountExtension { /// @dev Struct to hold target data. struct TargetData { address target; // The target contract. @@ -25,18 +25,18 @@ contract LlamaRewardsClaimAccountExtension is LlamaBaseAccountExtension { /// @dev Thrown if the target-selector is not authorized. error UnauthorizedTargetSelector(address target, bytes4 selector); - /// @notice The rewards claim storage contract. - LlamaRewardsClaimStorage public immutable REWARDS_CLAIM_STORAGE; + /// @notice The Llama account multicall storage contract. + LlamaAccountMulticallStorage public immutable ACCOUNT_MULTICALL_STORAGE; - /// @dev Initializes the Llama rewards claim account extenstion. - constructor(LlamaRewardsClaimStorage rewardsClaimStorage) { - REWARDS_CLAIM_STORAGE = rewardsClaimStorage; + /// @dev Initializes the Llama account multicall extenstion. + constructor(LlamaAccountMulticallStorage accountMulticallStorage) { + ACCOUNT_MULTICALL_STORAGE = accountMulticallStorage; } - /// @notice Claims rewards from the target contracts. - /// @param targetData The target data to claim rewards from. + /// @notice Multicalls on behalf of the Llama account. + /// @param targetData The target data to multicall. /// @return returnData The return data from the target calls. - function claimRewards(TargetData[] memory targetData) external onlyDelegateCall returns (bytes[] memory returnData) { + function multicall(TargetData[] memory targetData) external onlyDelegateCall returns (bytes[] memory returnData) { uint256 length = targetData.length; returnData = new bytes[](length); for (uint256 i = 0; i < length; i = LlamaUtils.uncheckedIncrement(i)) { @@ -46,7 +46,7 @@ contract LlamaRewardsClaimAccountExtension is LlamaBaseAccountExtension { bytes4 selector = bytes4(callData); // Check if the target-selector is authorized. - if (!REWARDS_CLAIM_STORAGE.authorizedTargetSelectors(target, selector)) { + if (!ACCOUNT_MULTICALL_STORAGE.authorizedTargetSelectors(target, selector)) { revert UnauthorizedTargetSelector(target, selector); } diff --git a/src/rewards-claimer/LlamaRewardsClaimGuard.sol b/src/account-multicall/LlamaAccountMulticallGuard.sol similarity index 57% rename from src/rewards-claimer/LlamaRewardsClaimGuard.sol rename to src/account-multicall/LlamaAccountMulticallGuard.sol index 13ecd7b..3487ae0 100644 --- a/src/rewards-claimer/LlamaRewardsClaimGuard.sol +++ b/src/account-multicall/LlamaAccountMulticallGuard.sol @@ -1,24 +1,24 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.23; +import {LlamaAccountMulticallExtension} from "src/account-multicall/LlamaAccountMulticallExtension.sol"; import {ILlamaActionGuard} from "src/interfaces/ILlamaActionGuard.sol"; import {ActionInfo} from "src/lib/Structs.sol"; -import {LlamaRewardsClaimAccountExtension} from "src/rewards-claimer/LlamaRewardsClaimAccountExtension.sol"; -/// @title Llama Rewards Claim Guard +/// @title Llama Account Multicall Guard /// @author Llama (devsdosomething@llama.xyz) -/// @notice A guard that only allows the `LlamaRewardsClaimAccountExtension.claimRewards` to be delegate-called +/// @notice A guard that only allows the `LlamaAccountMulticallExtension.multicall` to be delegate-called /// @dev This guard should be used to protect the `execute` function in the `LlamaAccount` contract -contract LlamaRewardsClaimGuard is ILlamaActionGuard { +contract LlamaAccountMulticallGuard is ILlamaActionGuard { /// @dev Thrown if the call is not authorized. error UnauthorizedCall(address target, bytes4 selector, bool withDelegatecall); - /// @notice The address of the `RewardsClaimer` account extension. - LlamaRewardsClaimAccountExtension public immutable REWARDS_CLAIMER; + /// @notice The address of the Llama account multicall extension. + LlamaAccountMulticallExtension public immutable ACCOUNT_MULTICALL_EXTENSION; - /// @dev Initializes the Llama rewards claim guard. - constructor(LlamaRewardsClaimAccountExtension rewardsClaimer) { - REWARDS_CLAIMER = rewardsClaimer; + /// @dev Initializes the Llama account multicall guard. + constructor(LlamaAccountMulticallExtension accountMulticallExtension) { + ACCOUNT_MULTICALL_EXTENSION = accountMulticallExtension; } /// @inheritdoc ILlamaActionGuard @@ -28,9 +28,10 @@ contract LlamaRewardsClaimGuard is ILlamaActionGuard { abi.decode(actionInfo.data[4:], (address, bool, uint256, bytes)); bytes4 selector = bytes4(data); - // Check if the target is the rewards claimer, selector is `claimRewards` and the call type is a delegatecall. + // Check if the target is the Llama account multicall extension, selector is `multicall` and the call type is a + // delegatecall. if ( - target != address(REWARDS_CLAIMER) || selector != LlamaRewardsClaimAccountExtension.claimRewards.selector + target != address(ACCOUNT_MULTICALL_EXTENSION) || selector != LlamaAccountMulticallExtension.multicall.selector || !withDelegatecall ) revert UnauthorizedCall(target, selector, withDelegatecall); } diff --git a/src/rewards-claimer/LlamaRewardsClaimStorage.sol b/src/account-multicall/LlamaAccountMulticallStorage.sol similarity index 89% rename from src/rewards-claimer/LlamaRewardsClaimStorage.sol rename to src/account-multicall/LlamaAccountMulticallStorage.sol index deeb3c3..d902813 100644 --- a/src/rewards-claimer/LlamaRewardsClaimStorage.sol +++ b/src/account-multicall/LlamaAccountMulticallStorage.sol @@ -3,11 +3,11 @@ pragma solidity 0.8.23; import {LlamaUtils} from "src/lib/LlamaUtils.sol"; -/// @title Llama Rewards Claim Storage +/// @title Llama Account Multicall Storage /// @author Llama (devsdosomething@llama.xyz) -/// @notice The storage contract for the `LlamaRewardsClaimAccountExtension` contract. +/// @notice The storage contract for the `LlamaAccountMulticallExtension` contract. /// @dev This is a separate storage contract to prevent storage collisions with the Llama account. -contract LlamaRewardsClaimStorage { +contract LlamaAccountMulticallStorage { /// @dev Struct to hold authorized target-selectors. struct TargetSelectorAuthorization { address target; // The target contract. @@ -27,7 +27,7 @@ contract LlamaRewardsClaimStorage { /// @notice Mapping of all authorized target-selectors. mapping(address target => mapping(bytes4 selector => bool isAuthorized)) public authorizedTargetSelectors; - /// @dev Initializes the Llama rewards claim storage. + /// @dev Initializes the Llama account multicall storage. constructor(address llamaExecutor, TargetSelectorAuthorization[] memory data) { LLAMA_EXECUTOR = llamaExecutor; _setAuthorizedTargetSelectors(data); diff --git a/test/rewards-claimer/LlamaRewardsClaimAccountExtension.t.sol b/test/account-multicall/LlamaAccountMulticallExtension.t.sol similarity index 74% rename from test/rewards-claimer/LlamaRewardsClaimAccountExtension.t.sol rename to test/account-multicall/LlamaAccountMulticallExtension.t.sol index a37c371..75b50c4 100644 --- a/test/rewards-claimer/LlamaRewardsClaimAccountExtension.t.sol +++ b/test/account-multicall/LlamaAccountMulticallExtension.t.sol @@ -6,37 +6,36 @@ import {Test, console2} from "forge-std/Test.sol"; import {IERC20} from "@openzeppelin/token/ERC20/IERC20.sol"; import {MockRewardsContract} from "test/mock/MockRewardsContract.sol"; -import {LlamaRewardsClaimTestSetup} from "test/rewards-claimer/LlamaRewardsClaimTestSetup.sol"; +import {LlamaAccountMulticallTestSetup} from "test/account-multicall/LlamaAccountMulticallTestSetup.sol"; import {ILlamaAccount} from "src/interfaces/ILlamaAccount.sol"; import {ILlamaCore} from "src/interfaces/ILlamaCore.sol"; import {ActionInfo} from "src/lib/Structs.sol"; -import {LlamaRewardsClaimAccountExtension} from "src/rewards-claimer/LlamaRewardsClaimAccountExtension.sol"; -import {LlamaRewardsClaimGuard} from "src/rewards-claimer/LlamaRewardsClaimGuard.sol"; -import {LlamaRewardsClaimStorage} from "src/rewards-claimer/LlamaRewardsClaimStorage.sol"; +import {LlamaAccountMulticallExtension} from "src/account-multicall/LlamaAccountMulticallExtension.sol"; +import {LlamaAccountMulticallStorage} from "src/account-multicall/LlamaAccountMulticallStorage.sol"; import {LlamaBaseAccountExtension} from "src/common/LlamaBaseAccountExtension.sol"; -contract LlamaRewardsClaimAccountExtensionTest is LlamaRewardsClaimTestSetup { +contract LlamaAccountMulticallExtensionTest is LlamaAccountMulticallTestSetup { event ethWithdrawn(address indexed from, address indexed to, uint256 amount); event erc20Withdrawn(IERC20 indexed token, address indexed from, address indexed to, uint256 amount); function setUp() public override { - LlamaRewardsClaimTestSetup.setUp(); + LlamaAccountMulticallTestSetup.setUp(); } } -contract Constructor is LlamaRewardsClaimAccountExtensionTest { - function test_SetsRewardsClaimStorage() public { - assertEq(address(rewardsClaimAccountExtension.REWARDS_CLAIM_STORAGE()), address(rewardsClaimStorage)); +contract Constructor is LlamaAccountMulticallExtensionTest { + function test_SetsAccountMulticallStorage() public { + assertEq(address(accountMulticallExtension.ACCOUNT_MULTICALL_STORAGE()), address(accountMulticallStorage)); } } -contract ClaimRewards is LlamaRewardsClaimAccountExtensionTest { +contract Multicall is LlamaAccountMulticallExtensionTest { function _createAndQueueActionClaimRewards() public returns (ActionInfo memory actionInfo) { - LlamaRewardsClaimAccountExtension.TargetData[] memory targetData = _setupClaimRewardsData(); - bytes memory accountExtensionData = abi.encodeCall(LlamaRewardsClaimAccountExtension.claimRewards, (targetData)); + LlamaAccountMulticallExtension.TargetData[] memory targetData = _setupClaimRewardsData(); + bytes memory accountExtensionData = abi.encodeCall(LlamaAccountMulticallExtension.multicall, (targetData)); bytes memory data = - abi.encodeCall(ILlamaAccount.execute, (address(rewardsClaimAccountExtension), true, 0, accountExtensionData)); + abi.encodeCall(ILlamaAccount.execute, (address(accountMulticallExtension), true, 0, accountExtensionData)); // Create an action to claim rewards. vm.prank(coreTeam1); @@ -52,7 +51,7 @@ contract ClaimRewards is LlamaRewardsClaimAccountExtensionTest { CORE.castApproval(CORE_TEAM_ROLE, actionInfo, ""); } - function test_ClaimRewards() public { + function test_Multicall() public { ActionInfo memory actionInfo = _createAndQueueActionClaimRewards(); assertEq(address(rewardsContract1).balance, 1 ether); @@ -112,20 +111,20 @@ contract ClaimRewards is LlamaRewardsClaimAccountExtensionTest { ActionInfo memory actionInfo = _createAndQueueActionClaimRewards(); // Unauthorize target selector. - LlamaRewardsClaimStorage.TargetSelectorAuthorization[] memory data = - new LlamaRewardsClaimStorage.TargetSelectorAuthorization[](1); - data[0] = LlamaRewardsClaimStorage.TargetSelectorAuthorization( + LlamaAccountMulticallStorage.TargetSelectorAuthorization[] memory data = + new LlamaAccountMulticallStorage.TargetSelectorAuthorization[](1); + data[0] = LlamaAccountMulticallStorage.TargetSelectorAuthorization( address(rewardsContract1), MockRewardsContract.withdrawETH.selector, false ); vm.prank(address(EXECUTOR)); - rewardsClaimStorage.setAuthorizedTargetSelectors(data); + accountMulticallStorage.setAuthorizedTargetSelectors(data); bytes memory expectedErr = abi.encodeWithSelector( ILlamaCore.FailedActionExecution.selector, abi.encodeWithSelector( ILlamaAccount.FailedExecution.selector, abi.encodeWithSelector( - LlamaRewardsClaimAccountExtension.UnauthorizedTargetSelector.selector, + LlamaAccountMulticallExtension.UnauthorizedTargetSelector.selector, address(rewardsContract1), MockRewardsContract.withdrawETH.selector ) @@ -136,8 +135,8 @@ contract ClaimRewards is LlamaRewardsClaimAccountExtensionTest { } function test_RevertIf_NotDelegateCalled() public { - LlamaRewardsClaimAccountExtension.TargetData[] memory targetData = _setupClaimRewardsData(); + LlamaAccountMulticallExtension.TargetData[] memory targetData = _setupClaimRewardsData(); vm.expectRevert(LlamaBaseAccountExtension.OnlyDelegateCall.selector); - rewardsClaimAccountExtension.claimRewards(targetData); + accountMulticallExtension.multicall(targetData); } } diff --git a/test/account-multicall/LlamaAccountMulticallGuard.t.sol b/test/account-multicall/LlamaAccountMulticallGuard.t.sol new file mode 100644 index 0000000..004d64c --- /dev/null +++ b/test/account-multicall/LlamaAccountMulticallGuard.t.sol @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import {Test, console2} from "forge-std/Test.sol"; + +import {LlamaAccountMulticallTestSetup} from "test/account-multicall/LlamaAccountMulticallTestSetup.sol"; + +import {ILlamaAccount} from "src/interfaces/ILlamaAccount.sol"; +import {LlamaAccountMulticallExtension} from "src/account-multicall/LlamaAccountMulticallExtension.sol"; +import {LlamaAccountMulticallGuard} from "src/account-multicall/LlamaAccountMulticallGuard.sol"; + +contract LlamaAccountMulticallGuardTest is LlamaAccountMulticallTestSetup { + function setUp() public override { + LlamaAccountMulticallTestSetup.setUp(); + } +} + +contract Constructor is LlamaAccountMulticallGuardTest { + function test_SetsAccountMulticallExtension() public { + assertEq(address(accountMulticallGuard.ACCOUNT_MULTICALL_EXTENSION()), address(accountMulticallExtension)); + } +} + +contract ValidateActionCreation is LlamaAccountMulticallGuardTest { + function test_PositiveFlow() public { + LlamaAccountMulticallExtension.TargetData[] memory targetData = _setupClaimRewardsData(); + + bytes memory accountExtensionData = abi.encodeCall(LlamaAccountMulticallExtension.multicall, (targetData)); + bytes memory data = + abi.encodeCall(ILlamaAccount.execute, (address(accountMulticallExtension), true, 0, accountExtensionData)); + + assertEq(CORE.actionsCount(), 12); + + vm.prank(coreTeam1); + CORE.createAction(CORE_TEAM_ROLE, STRATEGY, address(ACCOUNT), 0, data, ""); + + assertEq(CORE.actionsCount(), 13); + } + + function test_RevertIf_TargetIsNotAccountMulticallExtension() public { + LlamaAccountMulticallExtension.TargetData[] memory targetData = _setupClaimRewardsData(); + + address dummyTarget = address(0xdeadbeef); + + bytes memory accountExtensionData = abi.encodeCall(LlamaAccountMulticallExtension.multicall, (targetData)); + bytes memory data = abi.encodeCall(ILlamaAccount.execute, (dummyTarget, true, 0, accountExtensionData)); + + vm.prank(coreTeam1); + vm.expectRevert( + abi.encodeWithSelector( + LlamaAccountMulticallGuard.UnauthorizedCall.selector, + dummyTarget, + LlamaAccountMulticallExtension.multicall.selector, + true + ) + ); + CORE.createAction(CORE_TEAM_ROLE, STRATEGY, address(ACCOUNT), 0, data, ""); + } + + function test_RevertIf_SelectorIsNotMulticall() public { + LlamaAccountMulticallExtension.TargetData[] memory targetData = _setupClaimRewardsData(); + + bytes4 dummySelector = bytes4(0); + + bytes memory accountExtensionData = abi.encodeWithSelector(dummySelector, targetData); + bytes memory data = + abi.encodeCall(ILlamaAccount.execute, (address(accountMulticallExtension), true, 0, accountExtensionData)); + + vm.prank(coreTeam1); + vm.expectRevert( + abi.encodeWithSelector( + LlamaAccountMulticallGuard.UnauthorizedCall.selector, address(accountMulticallExtension), dummySelector, true + ) + ); + CORE.createAction(CORE_TEAM_ROLE, STRATEGY, address(ACCOUNT), 0, data, ""); + } + + function test_RevertIf_NotDelegateCall() public { + LlamaAccountMulticallExtension.TargetData[] memory targetData = _setupClaimRewardsData(); + + bytes memory accountExtensionData = abi.encodeCall(LlamaAccountMulticallExtension.multicall, (targetData)); + bytes memory data = + abi.encodeCall(ILlamaAccount.execute, (address(accountMulticallExtension), false, 0, accountExtensionData)); + + vm.prank(coreTeam1); + vm.expectRevert( + abi.encodeWithSelector( + LlamaAccountMulticallGuard.UnauthorizedCall.selector, + address(accountMulticallExtension), + LlamaAccountMulticallExtension.multicall.selector, + false + ) + ); + CORE.createAction(CORE_TEAM_ROLE, STRATEGY, address(ACCOUNT), 0, data, ""); + } +} diff --git a/test/account-multicall/LlamaAccountMulticallStorage.t.sol b/test/account-multicall/LlamaAccountMulticallStorage.t.sol new file mode 100644 index 0000000..dc2e6e0 --- /dev/null +++ b/test/account-multicall/LlamaAccountMulticallStorage.t.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import {Test, console2} from "forge-std/Test.sol"; + +import {LlamaAccountMulticallTestSetup} from "test/account-multicall/LlamaAccountMulticallTestSetup.sol"; + +import {LlamaAccountMulticallStorage} from "src/account-multicall/LlamaAccountMulticallStorage.sol"; + +contract LlamaAccountMulticallStorageTest is LlamaAccountMulticallTestSetup { + event TargetSelectorAuthorized(address indexed target, bytes4 indexed selector, bool isAuthorized); + + function setUp() public override { + LlamaAccountMulticallTestSetup.setUp(); + } +} + +contract Constructor is LlamaAccountMulticallStorageTest { + function test_SetsLlamaExecutor() public { + assertEq(address(accountMulticallStorage.LLAMA_EXECUTOR()), address(EXECUTOR)); + } + + function test_TargetSelectorAuthorization() public { + assertTrue(accountMulticallStorage.authorizedTargetSelectors(address(0xdeadbeef), bytes4(keccak256("withdraw()")))); + } +} + +contract SetAuthorizedTargetSelectors is LlamaAccountMulticallStorageTest { + function test_RevertIf_CallerIsNotLlama() public { + LlamaAccountMulticallStorage.TargetSelectorAuthorization[] memory data = + new LlamaAccountMulticallStorage.TargetSelectorAuthorization[](1); + data[0] = LlamaAccountMulticallStorage.TargetSelectorAuthorization( + address(0xdeadbeef), bytes4(keccak256("withdraw()")), false + ); + + vm.expectRevert(LlamaAccountMulticallStorage.OnlyLlama.selector); + accountMulticallStorage.setAuthorizedTargetSelectors(data); + } + + function test_SetAuthorizedTargetSelectors() public { + LlamaAccountMulticallStorage.TargetSelectorAuthorization[] memory data = + new LlamaAccountMulticallStorage.TargetSelectorAuthorization[](1); + data[0] = LlamaAccountMulticallStorage.TargetSelectorAuthorization( + address(0xdeadbeef), bytes4(keccak256("withdraw()")), false + ); + + assertTrue(accountMulticallStorage.authorizedTargetSelectors(address(0xdeadbeef), bytes4(keccak256("withdraw()")))); + + vm.prank(address(EXECUTOR)); + vm.expectEmit(); + emit TargetSelectorAuthorized(address(0xdeadbeef), bytes4(keccak256("withdraw()")), false); + accountMulticallStorage.setAuthorizedTargetSelectors(data); + + assertFalse(accountMulticallStorage.authorizedTargetSelectors(address(0xdeadbeef), bytes4(keccak256("withdraw()")))); + } +} diff --git a/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol b/test/account-multicall/LlamaAccountMulticallTestSetup.sol similarity index 65% rename from test/rewards-claimer/LlamaRewardsClaimTestSetup.sol rename to test/account-multicall/LlamaAccountMulticallTestSetup.sol index 865ad14..793ce15 100644 --- a/test/rewards-claimer/LlamaRewardsClaimTestSetup.sol +++ b/test/account-multicall/LlamaAccountMulticallTestSetup.sol @@ -10,14 +10,14 @@ import {IERC20} from "@openzeppelin/token/ERC20/IERC20.sol"; import {LlamaPeripheryTestSetup} from "test/LlamaPeripheryTestSetup.sol"; import {MockRewardsContract} from "test/mock/MockRewardsContract.sol"; -import {DeployLlamaRewardsClaimer} from "script/DeployLlamaRewardsClaimer.s.sol"; +import {DeployLlamaAccountMulticall} from "script/DeployLlamaAccountMulticall.s.sol"; import {ILlamaAccount} from "src/interfaces/ILlamaAccount.sol"; import {ILlamaPolicy} from "src/interfaces/ILlamaPolicy.sol"; -import {LlamaRewardsClaimAccountExtension} from "src/rewards-claimer/LlamaRewardsClaimAccountExtension.sol"; -import {LlamaRewardsClaimStorage} from "src/rewards-claimer/LlamaRewardsClaimStorage.sol"; +import {LlamaAccountMulticallExtension} from "src/account-multicall/LlamaAccountMulticallExtension.sol"; +import {LlamaAccountMulticallStorage} from "src/account-multicall/LlamaAccountMulticallStorage.sol"; -contract LlamaRewardsClaimTestSetup is LlamaPeripheryTestSetup, DeployLlamaRewardsClaimer { +contract LlamaAccountMulticallTestSetup is LlamaPeripheryTestSetup, DeployLlamaAccountMulticall { // Sample ERC20 token IERC20 public constant USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); @@ -31,8 +31,8 @@ contract LlamaRewardsClaimTestSetup is LlamaPeripheryTestSetup, DeployLlamaRewar function setUp() public virtual override { LlamaPeripheryTestSetup.setUp(); - // Deploy the Llama rewards claim contracts. - DeployLlamaRewardsClaimer.run("mockRewardsClaimerConfig.json"); + // Deploy the Llama account multicall contracts. + DeployLlamaAccountMulticall.run("mockAccountMulticallConfig.json"); // Deploy the mock rewards contracts and set LlamaAccount as the reward claimer. // Deal ETH and UNI to the rewards contracts. @@ -58,42 +58,42 @@ contract LlamaRewardsClaimTestSetup is LlamaPeripheryTestSetup, DeployLlamaRewar vm.startPrank(address(EXECUTOR)); // Authorize the rewards contracts to claim rewards. - LlamaRewardsClaimStorage.TargetSelectorAuthorization[] memory data = - new LlamaRewardsClaimStorage.TargetSelectorAuthorization[](10); - data[0] = LlamaRewardsClaimStorage.TargetSelectorAuthorization( + LlamaAccountMulticallStorage.TargetSelectorAuthorization[] memory data = + new LlamaAccountMulticallStorage.TargetSelectorAuthorization[](10); + data[0] = LlamaAccountMulticallStorage.TargetSelectorAuthorization( address(rewardsContract1), MockRewardsContract.withdrawETH.selector, true ); - data[1] = LlamaRewardsClaimStorage.TargetSelectorAuthorization( + data[1] = LlamaAccountMulticallStorage.TargetSelectorAuthorization( address(rewardsContract1), MockRewardsContract.withdrawERC20.selector, true ); - data[2] = LlamaRewardsClaimStorage.TargetSelectorAuthorization( + data[2] = LlamaAccountMulticallStorage.TargetSelectorAuthorization( address(rewardsContract2), MockRewardsContract.withdrawETH.selector, true ); - data[3] = LlamaRewardsClaimStorage.TargetSelectorAuthorization( + data[3] = LlamaAccountMulticallStorage.TargetSelectorAuthorization( address(rewardsContract2), MockRewardsContract.withdrawERC20.selector, true ); - data[4] = LlamaRewardsClaimStorage.TargetSelectorAuthorization( + data[4] = LlamaAccountMulticallStorage.TargetSelectorAuthorization( address(rewardsContract3), MockRewardsContract.withdrawETH.selector, true ); - data[5] = LlamaRewardsClaimStorage.TargetSelectorAuthorization( + data[5] = LlamaAccountMulticallStorage.TargetSelectorAuthorization( address(rewardsContract3), MockRewardsContract.withdrawERC20.selector, true ); - data[6] = LlamaRewardsClaimStorage.TargetSelectorAuthorization( + data[6] = LlamaAccountMulticallStorage.TargetSelectorAuthorization( address(rewardsContract4), MockRewardsContract.withdrawETH.selector, true ); - data[7] = LlamaRewardsClaimStorage.TargetSelectorAuthorization( + data[7] = LlamaAccountMulticallStorage.TargetSelectorAuthorization( address(rewardsContract4), MockRewardsContract.withdrawERC20.selector, true ); - data[8] = LlamaRewardsClaimStorage.TargetSelectorAuthorization( + data[8] = LlamaAccountMulticallStorage.TargetSelectorAuthorization( address(rewardsContract5), MockRewardsContract.withdrawETH.selector, true ); - data[9] = LlamaRewardsClaimStorage.TargetSelectorAuthorization( + data[9] = LlamaAccountMulticallStorage.TargetSelectorAuthorization( address(rewardsContract5), MockRewardsContract.withdrawERC20.selector, true ); - rewardsClaimStorage.setAuthorizedTargetSelectors(data); + accountMulticallStorage.setAuthorizedTargetSelectors(data); - // Set the Llama rewards claim guard. - CORE.setGuard(address(ACCOUNT), ILlamaAccount.execute.selector, address(rewardsClaimGuard)); + // Set the Llama account multicall guard. + CORE.setGuard(address(ACCOUNT), ILlamaAccount.execute.selector, address(accountMulticallGuard)); // Assign LlamaAccount.execute permission to Core Team role. POLICY.setRolePermission( @@ -108,40 +108,36 @@ contract LlamaRewardsClaimTestSetup is LlamaPeripheryTestSetup, DeployLlamaRewar // ======== Helpers ======== // ========================= - function _setupClaimRewardsData() - public - view - returns (LlamaRewardsClaimAccountExtension.TargetData[] memory targetData) - { - targetData = new LlamaRewardsClaimAccountExtension.TargetData[](10); - targetData[0] = LlamaRewardsClaimAccountExtension.TargetData( + function _setupClaimRewardsData() public view returns (LlamaAccountMulticallExtension.TargetData[] memory targetData) { + targetData = new LlamaAccountMulticallExtension.TargetData[](10); + targetData[0] = LlamaAccountMulticallExtension.TargetData( address(rewardsContract1), 0, abi.encodeCall(MockRewardsContract.withdrawETH, ()) ); - targetData[1] = LlamaRewardsClaimAccountExtension.TargetData( + targetData[1] = LlamaAccountMulticallExtension.TargetData( address(rewardsContract1), 0, abi.encodeCall(MockRewardsContract.withdrawERC20, (USDC)) ); - targetData[2] = LlamaRewardsClaimAccountExtension.TargetData( + targetData[2] = LlamaAccountMulticallExtension.TargetData( address(rewardsContract2), 0, abi.encodeCall(MockRewardsContract.withdrawETH, ()) ); - targetData[3] = LlamaRewardsClaimAccountExtension.TargetData( + targetData[3] = LlamaAccountMulticallExtension.TargetData( address(rewardsContract2), 0, abi.encodeCall(MockRewardsContract.withdrawERC20, (USDC)) ); - targetData[4] = LlamaRewardsClaimAccountExtension.TargetData( + targetData[4] = LlamaAccountMulticallExtension.TargetData( address(rewardsContract3), 0, abi.encodeCall(MockRewardsContract.withdrawETH, ()) ); - targetData[5] = LlamaRewardsClaimAccountExtension.TargetData( + targetData[5] = LlamaAccountMulticallExtension.TargetData( address(rewardsContract3), 0, abi.encodeCall(MockRewardsContract.withdrawERC20, (USDC)) ); - targetData[6] = LlamaRewardsClaimAccountExtension.TargetData( + targetData[6] = LlamaAccountMulticallExtension.TargetData( address(rewardsContract4), 0, abi.encodeCall(MockRewardsContract.withdrawETH, ()) ); - targetData[7] = LlamaRewardsClaimAccountExtension.TargetData( + targetData[7] = LlamaAccountMulticallExtension.TargetData( address(rewardsContract4), 0, abi.encodeCall(MockRewardsContract.withdrawERC20, (USDC)) ); - targetData[8] = LlamaRewardsClaimAccountExtension.TargetData( + targetData[8] = LlamaAccountMulticallExtension.TargetData( address(rewardsContract5), 0, abi.encodeCall(MockRewardsContract.withdrawETH, ()) ); - targetData[9] = LlamaRewardsClaimAccountExtension.TargetData( + targetData[9] = LlamaAccountMulticallExtension.TargetData( address(rewardsContract5), 0, abi.encodeCall(MockRewardsContract.withdrawERC20, (USDC)) ); } diff --git a/test/rewards-claimer/LlamaRewardsClaimGuard.t.sol b/test/rewards-claimer/LlamaRewardsClaimGuard.t.sol deleted file mode 100644 index 05a3bc1..0000000 --- a/test/rewards-claimer/LlamaRewardsClaimGuard.t.sol +++ /dev/null @@ -1,96 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {Test, console2} from "forge-std/Test.sol"; - -import {LlamaRewardsClaimTestSetup} from "test/rewards-claimer/LlamaRewardsClaimTestSetup.sol"; - -import {ILlamaAccount} from "src/interfaces/ILlamaAccount.sol"; -import {LlamaRewardsClaimAccountExtension} from "src/rewards-claimer/LlamaRewardsClaimAccountExtension.sol"; -import {LlamaRewardsClaimGuard} from "src/rewards-claimer/LlamaRewardsClaimGuard.sol"; - -contract LlamaRewardsClaimGuardTest is LlamaRewardsClaimTestSetup { - function setUp() public override { - LlamaRewardsClaimTestSetup.setUp(); - } -} - -contract Constructor is LlamaRewardsClaimGuardTest { - function test_SetsRewardsClaimer() public { - assertEq(address(rewardsClaimGuard.REWARDS_CLAIMER()), address(rewardsClaimAccountExtension)); - } -} - -contract ValidateActionCreation is LlamaRewardsClaimGuardTest { - function test_PositiveFlow() public { - LlamaRewardsClaimAccountExtension.TargetData[] memory targetData = _setupClaimRewardsData(); - - bytes memory accountExtensionData = abi.encodeCall(LlamaRewardsClaimAccountExtension.claimRewards, (targetData)); - bytes memory data = - abi.encodeCall(ILlamaAccount.execute, (address(rewardsClaimAccountExtension), true, 0, accountExtensionData)); - - assertEq(CORE.actionsCount(), 12); - - vm.prank(coreTeam1); - CORE.createAction(CORE_TEAM_ROLE, STRATEGY, address(ACCOUNT), 0, data, ""); - - assertEq(CORE.actionsCount(), 13); - } - - function test_RevertIf_TargetIsNotRewardsClaimer() public { - LlamaRewardsClaimAccountExtension.TargetData[] memory targetData = _setupClaimRewardsData(); - - address dummyTarget = address(0xdeadbeef); - - bytes memory accountExtensionData = abi.encodeCall(LlamaRewardsClaimAccountExtension.claimRewards, (targetData)); - bytes memory data = abi.encodeCall(ILlamaAccount.execute, (dummyTarget, true, 0, accountExtensionData)); - - vm.prank(coreTeam1); - vm.expectRevert( - abi.encodeWithSelector( - LlamaRewardsClaimGuard.UnauthorizedCall.selector, - dummyTarget, - LlamaRewardsClaimAccountExtension.claimRewards.selector, - true - ) - ); - CORE.createAction(CORE_TEAM_ROLE, STRATEGY, address(ACCOUNT), 0, data, ""); - } - - function test_RevertIf_SelectorIsNotClaimRewards() public { - LlamaRewardsClaimAccountExtension.TargetData[] memory targetData = _setupClaimRewardsData(); - - bytes4 dummySelector = bytes4(0); - - bytes memory accountExtensionData = abi.encodeWithSelector(dummySelector, targetData); - bytes memory data = - abi.encodeCall(ILlamaAccount.execute, (address(rewardsClaimAccountExtension), true, 0, accountExtensionData)); - - vm.prank(coreTeam1); - vm.expectRevert( - abi.encodeWithSelector( - LlamaRewardsClaimGuard.UnauthorizedCall.selector, address(rewardsClaimAccountExtension), dummySelector, true - ) - ); - CORE.createAction(CORE_TEAM_ROLE, STRATEGY, address(ACCOUNT), 0, data, ""); - } - - function test_RevertIf_NotDelegateCall() public { - LlamaRewardsClaimAccountExtension.TargetData[] memory targetData = _setupClaimRewardsData(); - - bytes memory accountExtensionData = abi.encodeCall(LlamaRewardsClaimAccountExtension.claimRewards, (targetData)); - bytes memory data = - abi.encodeCall(ILlamaAccount.execute, (address(rewardsClaimAccountExtension), false, 0, accountExtensionData)); - - vm.prank(coreTeam1); - vm.expectRevert( - abi.encodeWithSelector( - LlamaRewardsClaimGuard.UnauthorizedCall.selector, - address(rewardsClaimAccountExtension), - LlamaRewardsClaimAccountExtension.claimRewards.selector, - false - ) - ); - CORE.createAction(CORE_TEAM_ROLE, STRATEGY, address(ACCOUNT), 0, data, ""); - } -} diff --git a/test/rewards-claimer/LlamaRewardsClaimStorage.t.sol b/test/rewards-claimer/LlamaRewardsClaimStorage.t.sol deleted file mode 100644 index 8d1a2e1..0000000 --- a/test/rewards-claimer/LlamaRewardsClaimStorage.t.sol +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {Test, console2} from "forge-std/Test.sol"; - -import {LlamaRewardsClaimTestSetup} from "test/rewards-claimer/LlamaRewardsClaimTestSetup.sol"; - -import {LlamaRewardsClaimStorage} from "src/rewards-claimer/LlamaRewardsClaimStorage.sol"; - -contract LlamaRewardsClaimStorageTest is LlamaRewardsClaimTestSetup { - event TargetSelectorAuthorized(address indexed target, bytes4 indexed selector, bool isAuthorized); - - function setUp() public override { - LlamaRewardsClaimTestSetup.setUp(); - } -} - -contract Constructor is LlamaRewardsClaimStorageTest { - function test_SetsLlamaExecutor() public { - assertEq(address(rewardsClaimStorage.LLAMA_EXECUTOR()), address(EXECUTOR)); - } - - function test_TargetSelectorAuthorization() public { - assertTrue(rewardsClaimStorage.authorizedTargetSelectors(address(0xdeadbeef), bytes4(keccak256("withdraw()")))); - } -} - -contract SetAuthorizedTargetSelectors is LlamaRewardsClaimStorageTest { - function test_RevertIf_CallerIsNotLlama() public { - LlamaRewardsClaimStorage.TargetSelectorAuthorization[] memory data = - new LlamaRewardsClaimStorage.TargetSelectorAuthorization[](1); - data[0] = - LlamaRewardsClaimStorage.TargetSelectorAuthorization(address(0xdeadbeef), bytes4(keccak256("withdraw()")), false); - - vm.expectRevert(LlamaRewardsClaimStorage.OnlyLlama.selector); - rewardsClaimStorage.setAuthorizedTargetSelectors(data); - } - - function test_SetAuthorizedTargetSelectors() public { - LlamaRewardsClaimStorage.TargetSelectorAuthorization[] memory data = - new LlamaRewardsClaimStorage.TargetSelectorAuthorization[](1); - data[0] = - LlamaRewardsClaimStorage.TargetSelectorAuthorization(address(0xdeadbeef), bytes4(keccak256("withdraw()")), false); - - assertTrue(rewardsClaimStorage.authorizedTargetSelectors(address(0xdeadbeef), bytes4(keccak256("withdraw()")))); - - vm.prank(address(EXECUTOR)); - vm.expectEmit(); - emit TargetSelectorAuthorized(address(0xdeadbeef), bytes4(keccak256("withdraw()")), false); - rewardsClaimStorage.setAuthorizedTargetSelectors(data); - - assertFalse(rewardsClaimStorage.authorizedTargetSelectors(address(0xdeadbeef), bytes4(keccak256("withdraw()")))); - } -} From f8f6b7b7a64a1f5fea545ea77b26cce0fa6a9659 Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Thu, 11 Apr 2024 21:44:36 -0400 Subject: [PATCH 34/40] factory --- justfile | 21 ++++-- .../DeployLlamaAccountMulticallFactory.s.sol | 21 ++++++ ...> DeployLlamaAccountMulticallModule.s.sol} | 30 +++++---- .../input/1/mockAccountMulticallConfig.json | 2 + .../11155111/accountMulticallConfig.json | 2 + .../LlamaAccountMulticallFactory.sol | 66 +++++++++++++++++++ .../LlamaAccountMulticallStorage.sol | 46 ++++++++++++- test/LlamaPeripheryTestSetup.sol | 4 ++ .../LlamaAccountMulticallTestSetup.sol | 14 +++- 9 files changed, 183 insertions(+), 23 deletions(-) create mode 100644 script/DeployLlamaAccountMulticallFactory.s.sol rename script/{DeployLlamaAccountMulticall.s.sol => DeployLlamaAccountMulticallModule.s.sol} (66%) create mode 100644 src/account-multicall/LlamaAccountMulticallFactory.sol diff --git a/justfile b/justfile index f68eb32..f8e6cff 100644 --- a/justfile +++ b/justfile @@ -18,20 +18,31 @@ run-script script_name flags='' sig='' args='': -vvvv {{flags}} mv _test test -run-deploy-voting-module-script flags: (run-script 'DeployLlamaTokenVotingModule' flags '--sig "run(address,string)"' '$SCRIPT_DEPLOYER_ADDRESS "tokenVotingModuleConfig.json"') +# Token voting module dry-run-deploy: (run-script 'DeployLlamaTokenVotingFactory') deploy: (run-script 'DeployLlamaTokenVotingFactory' '--broadcast --verify --slow --build-info --build-info-path build_info') +verify: (run-script 'DeployLlamaTokenVotingFactory' '--verify --resume') + +run-deploy-voting-module-script flags: (run-script 'DeployLlamaTokenVotingModule' flags '--sig "run(address,string)"' '$SCRIPT_DEPLOYER_ADDRESS "tokenVotingModuleConfig.json"') + dry-run-deploy-voting-module: (run-deploy-voting-module-script '') deploy-voting-module: (run-deploy-voting-module-script '--broadcast --verify') -run-deploy-account-multicall-script flags: (run-script 'DeployLlamaAccountMulticall' flags '--sig "run(string)"' '"accountMulticallConfig.json"') +# Account multicall module -dry-run-deploy-account-multicall: (run-deploy-account-multicall-script '') +dry-run-deploy-acount-multicall-factory: (run-script 'DeployLlamaAccountMulticallFactory') -deploy-account-multicall: (run-deploy-account-multicall-script '--broadcast --verify --slow') +deploy-acount-multicall-factory: (run-script 'DeployLlamaAccountMulticallFactory' '--broadcast --verify --slow --build-info --build-info-path build_info') + +verify-account-multicall-factory: (run-script 'DeployLlamaAccountMulticallFactory' '--verify --resume') + +run-deploy-account-multicall-module-script flags: (run-script 'DeployLlamaAccountMulticallModule' flags '--sig "run(address,string)"' '$SCRIPT_DEPLOYER_ADDRESS "accountMulticallConfig.json"') + +dry-run-deploy-account-multicall-module: (run-deploy-account-multicall-module-script '') + +deploy-account-multicall-module: (run-deploy-account-multicall-module-script '--broadcast --verify') -verify: (run-script 'DeployLlamaTokenVotingFactory' '--verify --resume') diff --git a/script/DeployLlamaAccountMulticallFactory.s.sol b/script/DeployLlamaAccountMulticallFactory.s.sol new file mode 100644 index 0000000..f216f65 --- /dev/null +++ b/script/DeployLlamaAccountMulticallFactory.s.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +import {Script, stdJson} from "forge-std/Script.sol"; + +import {DeployUtils} from "script/DeployUtils.sol"; + +import {LlamaAccountMulticallFactory} from "src/account-multicall/LlamaAccountMulticallFactory.sol"; + +contract DeployLlamaAccountMulticallFactory is Script { + // Factory contracts. + LlamaAccountMulticallFactory accountMulticallFactory; + + function run() public { + DeployUtils.print(string.concat("Deploying Llama account multicall factory to chain:", vm.toString(block.chainid))); + + vm.broadcast(); + accountMulticallFactory = new LlamaAccountMulticallFactory(); + DeployUtils.print(string.concat(" LlamaAccountMulticallFactory: ", vm.toString(address(accountMulticallFactory)))); + } +} diff --git a/script/DeployLlamaAccountMulticall.s.sol b/script/DeployLlamaAccountMulticallModule.s.sol similarity index 66% rename from script/DeployLlamaAccountMulticall.s.sol rename to script/DeployLlamaAccountMulticallModule.s.sol index a8499a5..7998652 100644 --- a/script/DeployLlamaAccountMulticall.s.sol +++ b/script/DeployLlamaAccountMulticallModule.s.sol @@ -6,10 +6,11 @@ import {Script, stdJson} from "forge-std/Script.sol"; import {DeployUtils} from "script/DeployUtils.sol"; import {LlamaAccountMulticallExtension} from "src/account-multicall/LlamaAccountMulticallExtension.sol"; +import {LlamaAccountMulticallFactory} from "src/account-multicall/LlamaAccountMulticallFactory.sol"; import {LlamaAccountMulticallGuard} from "src/account-multicall/LlamaAccountMulticallGuard.sol"; import {LlamaAccountMulticallStorage} from "src/account-multicall/LlamaAccountMulticallStorage.sol"; -contract DeployLlamaAccountMulticall is Script { +contract DeployLlamaAccountMulticallModule is Script { using stdJson for string; struct TargetSelectorAuthorizationInputs { @@ -24,27 +25,30 @@ contract DeployLlamaAccountMulticall is Script { LlamaAccountMulticallExtension accountMulticallExtension; LlamaAccountMulticallGuard accountMulticallGuard; - function run(string memory configFile) public { + function run(address deployer, string memory configFile) public { string memory jsonInput = DeployUtils.readScriptInput(configFile); + LlamaAccountMulticallFactory factory = LlamaAccountMulticallFactory(jsonInput.readAddress(".factory")); + + DeployUtils.print(string.concat("Deploying Llama account multicall module to chain:", vm.toString(block.chainid))); + address llamaExecutor = jsonInput.readAddress(".llamaExecutor"); + uint256 nonce = jsonInput.readUint(".nonce"); LlamaAccountMulticallStorage.TargetSelectorAuthorization[] memory data = readTargetSelectorAuthorizations(jsonInput); + LlamaAccountMulticallFactory.LlamaAccountMulticallConfig memory config = + LlamaAccountMulticallFactory.LlamaAccountMulticallConfig(llamaExecutor, nonce, data); - DeployUtils.print(string.concat("Deploying Llama rewards claimer contracts to chain:", vm.toString(block.chainid))); - - vm.broadcast(); - accountMulticallStorage = new LlamaAccountMulticallStorage(llamaExecutor, data); - DeployUtils.print(string.concat(" LlamaAccountMulticallStorage: ", vm.toString(address(accountMulticallStorage)))); + vm.broadcast(deployer); + (accountMulticallGuard, accountMulticallExtension, accountMulticallStorage) = factory.deploy(config); - vm.broadcast(); - accountMulticallExtension = new LlamaAccountMulticallExtension(accountMulticallStorage); + DeployUtils.print("Successfully deployed a new Llama account multicall module"); + DeployUtils.print(string.concat(" LlamaAccountMulticallGuard: ", vm.toString(address(accountMulticallGuard)))); DeployUtils.print( string.concat(" LlamaAccountMulticallExtension: ", vm.toString(address(accountMulticallExtension))) ); - - vm.broadcast(); - accountMulticallGuard = new LlamaAccountMulticallGuard(accountMulticallExtension); - DeployUtils.print(string.concat(" LlamaAccountMulticallGuard: ", vm.toString(address(accountMulticallGuard)))); + DeployUtils.print( + string.concat(" LlamaAccountMulticallStorage: ", vm.toString(address(accountMulticallStorage))) + ); } function readTargetSelectorAuthorizations(string memory jsonInput) diff --git a/script/input/1/mockAccountMulticallConfig.json b/script/input/1/mockAccountMulticallConfig.json index 89d934e..e6f8648 100644 --- a/script/input/1/mockAccountMulticallConfig.json +++ b/script/input/1/mockAccountMulticallConfig.json @@ -1,6 +1,8 @@ { "comment": "This is an account multicall deployment for Forge tests.", + "factory": "0x90193C961A926261B756D1E5bb255e67ff9498A1", "llamaExecutor": "0xdAf00E9786cABB195a8a1Cf102730863aE94Dd75", + "nonce": 0, "initialTargetSelectorAuthorizations": [ { "comment": "DeadBeef.withdraw()", diff --git a/script/input/11155111/accountMulticallConfig.json b/script/input/11155111/accountMulticallConfig.json index c02bfcc..aa86c86 100644 --- a/script/input/11155111/accountMulticallConfig.json +++ b/script/input/11155111/accountMulticallConfig.json @@ -1,6 +1,8 @@ { "comment": "This is an example account multicall deployment on Sepolia.", + "factory": "0x0000000000000000000000000000000000000000", "llamaExecutor": "0x0000000000000000000000000000000000000000", + "nonce": 0, "initialTargetSelectorAuthorizations": [ { "comment": "Target::Selector", diff --git a/src/account-multicall/LlamaAccountMulticallFactory.sol b/src/account-multicall/LlamaAccountMulticallFactory.sol new file mode 100644 index 0000000..5f2fc75 --- /dev/null +++ b/src/account-multicall/LlamaAccountMulticallFactory.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +import {LlamaAccountMulticallExtension} from "src/account-multicall/LlamaAccountMulticallExtension.sol"; +import {LlamaAccountMulticallGuard} from "src/account-multicall/LlamaAccountMulticallGuard.sol"; +import {LlamaAccountMulticallStorage} from "src/account-multicall/LlamaAccountMulticallStorage.sol"; + +/// @title LlamaAccountMulticallFactory +/// @author Llama (devsdosomething@llama.xyz) +/// @notice This contract enables Llama instances to deploy an account multicall module. +contract LlamaAccountMulticallFactory { + /// @dev Configuration of new Llama account multicall module. + struct LlamaAccountMulticallConfig { + address llamaExecutor; // The address of the Llama executor. + uint256 nonce; // The nonce of the new account multicall module. + LlamaAccountMulticallStorage.TargetSelectorAuthorization[] data; // The target-selectors to authorize. + } + + /// @dev Emitted when a new Llama account multicall module is created. + event LlamaAccountMulticallModuleCreated( + address indexed deployer, + address indexed llamaExecutor, + uint256 nonce, + address accountMulticallGuard, + address accountMulticallExtension, + address accountMulticallStorage, + uint256 chainId + ); + + /// @notice Deploys a new Llama account multicall module. + /// @param accountMulticallConfig The configuration of the new Llama account multicall module. + /// @return accountMulticallGuard The deployed account multicall guard. + /// @return accountMulticallExtension The deployed account multicall extension. + /// @return accountMulticallStorage The deployed account multicall storage. + function deploy(LlamaAccountMulticallConfig memory accountMulticallConfig) + external + returns ( + LlamaAccountMulticallGuard accountMulticallGuard, + LlamaAccountMulticallExtension accountMulticallExtension, + LlamaAccountMulticallStorage accountMulticallStorage + ) + { + bytes32 salt = + keccak256(abi.encodePacked(msg.sender, accountMulticallConfig.llamaExecutor, accountMulticallConfig.nonce)); + + // Deploy and initialize account multicall storage. + accountMulticallStorage = new LlamaAccountMulticallStorage{salt: salt}(accountMulticallConfig.llamaExecutor); + accountMulticallStorage.initializeAuthorizedTargetSelectors(accountMulticallConfig.data); + + // Deploy account multicall extension. + accountMulticallExtension = new LlamaAccountMulticallExtension{salt: salt}(accountMulticallStorage); + + // Deploy account multicall guard. + accountMulticallGuard = new LlamaAccountMulticallGuard{salt: salt}(accountMulticallExtension); + + emit LlamaAccountMulticallModuleCreated( + msg.sender, + accountMulticallConfig.llamaExecutor, + accountMulticallConfig.nonce, + address(accountMulticallGuard), + address(accountMulticallExtension), + address(accountMulticallStorage), + block.chainid + ); + } +} diff --git a/src/account-multicall/LlamaAccountMulticallStorage.sol b/src/account-multicall/LlamaAccountMulticallStorage.sol index d902813..140d4ae 100644 --- a/src/account-multicall/LlamaAccountMulticallStorage.sol +++ b/src/account-multicall/LlamaAccountMulticallStorage.sol @@ -8,6 +8,10 @@ import {LlamaUtils} from "src/lib/LlamaUtils.sol"; /// @notice The storage contract for the `LlamaAccountMulticallExtension` contract. /// @dev This is a separate storage contract to prevent storage collisions with the Llama account. contract LlamaAccountMulticallStorage { + // ========================= + // ======== Structs ======== + // ========================= + /// @dev Struct to hold authorized target-selectors. struct TargetSelectorAuthorization { address target; // The target contract. @@ -15,24 +19,58 @@ contract LlamaAccountMulticallStorage { bool isAuthorized; // Is the target-selector authorized. } + // ======================== + // ======== Errors ======== + // ======================== + + /// @dev Thrown if `initializeAuthorizedTargetSelectors` is called again. + error AlreadyInitialized(); + /// @dev Only callable by a Llama instance's executor. error OnlyLlama(); + // ======================== + // ======== Events ======== + // ======================== + /// @notice Emitted when a target-selector is authorized. event TargetSelectorAuthorized(address indexed target, bytes4 indexed selector, bool isAuthorized); + // =================================== + // ======== Storage Variables ======== + // =================================== + /// @notice The Llama instance's executor. address public immutable LLAMA_EXECUTOR; + /// @dev Whether the contract is initialized. + bool internal initialized; + /// @notice Mapping of all authorized target-selectors. mapping(address target => mapping(bytes4 selector => bool isAuthorized)) public authorizedTargetSelectors; - /// @dev Initializes the Llama account multicall storage. - constructor(address llamaExecutor, TargetSelectorAuthorization[] memory data) { + // ====================================================== + // ======== Contract Creation and Initialization ======== + // ====================================================== + + /// @dev Sets the Llama executor. + constructor(address llamaExecutor) { LLAMA_EXECUTOR = llamaExecutor; + } + + /// @notice Initializes the authorized target-selectors. + /// @dev This function can only be called once. It should be called as part of the contract deployment. + /// @param data The target-selectors to authorize. + function initializeAuthorizedTargetSelectors(TargetSelectorAuthorization[] memory data) external { + if (initialized) revert AlreadyInitialized(); + initialized = true; _setAuthorizedTargetSelectors(data); } + // ================================ + // ======== External Logic ======== + // ================================ + /// @notice Sets the authorized target-selectors. /// @param data The target-selectors to authorize. function setAuthorizedTargetSelectors(TargetSelectorAuthorization[] memory data) external { @@ -40,6 +78,10 @@ contract LlamaAccountMulticallStorage { _setAuthorizedTargetSelectors(data); } + // ================================ + // ======== Internal Logic ======== + // ================================ + /// @dev Sets the authorized target-selectors. function _setAuthorizedTargetSelectors(TargetSelectorAuthorization[] memory data) internal { uint256 length = data.length; diff --git a/test/LlamaPeripheryTestSetup.sol b/test/LlamaPeripheryTestSetup.sol index 54f0392..a86f73d 100644 --- a/test/LlamaPeripheryTestSetup.sol +++ b/test/LlamaPeripheryTestSetup.sol @@ -36,6 +36,10 @@ contract LlamaPeripheryTestSetup is Test { address coreTeam4 = 0x6b45E38c87bfCa15ee90AAe2AFe3CFC58cE08F75; address coreTeam5 = 0xbdfcE43E5D2C7AA8599290d940c9932B8dBC94Ca; + // This is the address that we're using to deploy modules. It could be + // replaced with any address that we hold the private key for. + address LLAMA_MODULE_DEPLOYER = 0x3d9fEa8AeD0249990133132Bb4BC8d07C6a8259a; + // Mock protocol for action targets. MockProtocol public mockProtocol; diff --git a/test/account-multicall/LlamaAccountMulticallTestSetup.sol b/test/account-multicall/LlamaAccountMulticallTestSetup.sol index 793ce15..dffba11 100644 --- a/test/account-multicall/LlamaAccountMulticallTestSetup.sol +++ b/test/account-multicall/LlamaAccountMulticallTestSetup.sol @@ -10,14 +10,19 @@ import {IERC20} from "@openzeppelin/token/ERC20/IERC20.sol"; import {LlamaPeripheryTestSetup} from "test/LlamaPeripheryTestSetup.sol"; import {MockRewardsContract} from "test/mock/MockRewardsContract.sol"; -import {DeployLlamaAccountMulticall} from "script/DeployLlamaAccountMulticall.s.sol"; +import {DeployLlamaAccountMulticallFactory} from "script/DeployLlamaAccountMulticallFactory.s.sol"; +import {DeployLlamaAccountMulticallModule} from "script/DeployLlamaAccountMulticallModule.s.sol"; import {ILlamaAccount} from "src/interfaces/ILlamaAccount.sol"; import {ILlamaPolicy} from "src/interfaces/ILlamaPolicy.sol"; import {LlamaAccountMulticallExtension} from "src/account-multicall/LlamaAccountMulticallExtension.sol"; import {LlamaAccountMulticallStorage} from "src/account-multicall/LlamaAccountMulticallStorage.sol"; -contract LlamaAccountMulticallTestSetup is LlamaPeripheryTestSetup, DeployLlamaAccountMulticall { +contract LlamaAccountMulticallTestSetup is + LlamaPeripheryTestSetup, + DeployLlamaAccountMulticallFactory, + DeployLlamaAccountMulticallModule +{ // Sample ERC20 token IERC20 public constant USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); @@ -31,8 +36,11 @@ contract LlamaAccountMulticallTestSetup is LlamaPeripheryTestSetup, DeployLlamaA function setUp() public virtual override { LlamaPeripheryTestSetup.setUp(); + // Deploy the factory + DeployLlamaAccountMulticallFactory.run(); + // Deploy the Llama account multicall contracts. - DeployLlamaAccountMulticall.run("mockAccountMulticallConfig.json"); + DeployLlamaAccountMulticallModule.run(LLAMA_MODULE_DEPLOYER, "mockAccountMulticallConfig.json"); // Deploy the mock rewards contracts and set LlamaAccount as the reward claimer. // Deal ETH and UNI to the rewards contracts. From 95581e1804eed06cf1edae973819f8009845be6b Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Fri, 12 Apr 2024 16:07:57 -0400 Subject: [PATCH 35/40] InitializeAuthorizedTargetSelectors --- .../LlamaAccountMulticallStorage.t.sol | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/test/account-multicall/LlamaAccountMulticallStorage.t.sol b/test/account-multicall/LlamaAccountMulticallStorage.t.sol index dc2e6e0..73d28c5 100644 --- a/test/account-multicall/LlamaAccountMulticallStorage.t.sol +++ b/test/account-multicall/LlamaAccountMulticallStorage.t.sol @@ -19,10 +19,21 @@ contract Constructor is LlamaAccountMulticallStorageTest { function test_SetsLlamaExecutor() public { assertEq(address(accountMulticallStorage.LLAMA_EXECUTOR()), address(EXECUTOR)); } +} - function test_TargetSelectorAuthorization() public { +contract InitializeAuthorizedTargetSelectors is LlamaAccountMulticallStorageTest { + function test_InitializeAuthorizedTargetSelectors() public { assertTrue(accountMulticallStorage.authorizedTargetSelectors(address(0xdeadbeef), bytes4(keccak256("withdraw()")))); } + + function test_RevertIf_AlreadyInitialized() public { + LlamaAccountMulticallStorage.TargetSelectorAuthorization[] memory data = + new LlamaAccountMulticallStorage.TargetSelectorAuthorization[](0); + + vm.prank(address(EXECUTOR)); + vm.expectRevert(LlamaAccountMulticallStorage.AlreadyInitialized.selector); + accountMulticallStorage.initializeAuthorizedTargetSelectors(data); + } } contract SetAuthorizedTargetSelectors is LlamaAccountMulticallStorageTest { From 5d3d26d5b97ea170723a3bb035b184eef196651d Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Fri, 12 Apr 2024 16:27:09 -0400 Subject: [PATCH 36/40] initial factory test --- .../LlamaAccountMulticallFactory.t.sol | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 test/account-multicall/LlamaAccountMulticallFactory.t.sol diff --git a/test/account-multicall/LlamaAccountMulticallFactory.t.sol b/test/account-multicall/LlamaAccountMulticallFactory.t.sol new file mode 100644 index 0000000..536856a --- /dev/null +++ b/test/account-multicall/LlamaAccountMulticallFactory.t.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import {Test, console2} from "forge-std/Test.sol"; + +import {LlamaPeripheryTestSetup} from "test/LlamaPeripheryTestSetup.sol"; + +import {DeployLlamaAccountMulticallFactory} from "script/DeployLlamaAccountMulticallFactory.s.sol"; + +import {LlamaAccountMulticallExtension} from "src/account-multicall/LlamaAccountMulticallExtension.sol"; +import {LlamaAccountMulticallFactory} from "src/account-multicall/LlamaAccountMulticallFactory.sol"; +import {LlamaAccountMulticallGuard} from "src/account-multicall/LlamaAccountMulticallGuard.sol"; +import {LlamaAccountMulticallStorage} from "src/account-multicall/LlamaAccountMulticallStorage.sol"; + +contract LlamaAccountMulticallFactoryTest is LlamaPeripheryTestSetup, DeployLlamaAccountMulticallFactory { + event LlamaAccountMulticallModuleCreated( + address indexed deployer, + address indexed llamaExecutor, + uint256 nonce, + address accountMulticallGuard, + address accountMulticallExtension, + address accountMulticallStorage, + uint256 chainId + ); + + function setUp() public override { + LlamaPeripheryTestSetup.setUp(); + + // Deploy the factory + DeployLlamaAccountMulticallFactory.run(); + } +} + +contract DeployAccountMulticallModule is LlamaAccountMulticallFactoryTest { + function test_Deploy() public { + LlamaAccountMulticallStorage.TargetSelectorAuthorization[] memory data = + new LlamaAccountMulticallStorage.TargetSelectorAuthorization[](1); + LlamaAccountMulticallFactory.LlamaAccountMulticallConfig memory config = + LlamaAccountMulticallFactory.LlamaAccountMulticallConfig(address(EXECUTOR), 0, data); + + // vm.expectEmit(); + // emit LlamaAccountMulticallModuleCreated( + // address(this), address(EXECUTOR), 0, address(0), address(0), address(0), block.chainid + // ); + ( + LlamaAccountMulticallGuard accountMulticallGuard, + LlamaAccountMulticallExtension accountMulticallExtension, + LlamaAccountMulticallStorage accountMulticallStorage + ) = accountMulticallFactory.deploy(config); + + assertEq(address(accountMulticallGuard.ACCOUNT_MULTICALL_EXTENSION()), address(accountMulticallExtension)); + assertEq(address(accountMulticallExtension.ACCOUNT_MULTICALL_STORAGE()), address(accountMulticallStorage)); + assertEq(address(accountMulticallStorage.LLAMA_EXECUTOR()), address(EXECUTOR)); + } +} From d0d8c36378c8dfad29f8d4415ef02eff7f033da9 Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Fri, 12 Apr 2024 17:02:28 -0400 Subject: [PATCH 37/40] factory deploy test --- .../LlamaAccountMulticallFactory.t.sol | 62 +++++++++++++++++-- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/test/account-multicall/LlamaAccountMulticallFactory.t.sol b/test/account-multicall/LlamaAccountMulticallFactory.t.sol index 536856a..4fe5014 100644 --- a/test/account-multicall/LlamaAccountMulticallFactory.t.sol +++ b/test/account-multicall/LlamaAccountMulticallFactory.t.sol @@ -29,19 +29,72 @@ contract LlamaAccountMulticallFactoryTest is LlamaPeripheryTestSetup, DeployLlam // Deploy the factory DeployLlamaAccountMulticallFactory.run(); } + + // ========================= + // ======== Helpers ======== + // ========================= + + function _getCreate2Address(address deployer, uint256 salt, bytes memory bytecode) public pure returns (address) { + bytes32 hash = keccak256(abi.encodePacked(bytes1(0xff), deployer, salt, keccak256(bytecode))); + + // NOTE: cast last 20 bytes of hash to address + return address(uint160(uint256(hash))); + } + + function _preComputeMulticallAddresses(address moduleDeployer, uint256 nonce) + public + view + returns ( + address accountMulticallGuardAddress, + address accountMulticallExtensionAddress, + address accountMulticallStorageAddress + ) + { + bytes32 salt = keccak256(abi.encodePacked(moduleDeployer, address(EXECUTOR), nonce)); + + bytes memory multicallStorageBytecode = + abi.encodePacked(type(LlamaAccountMulticallStorage).creationCode, abi.encode(address(EXECUTOR))); + accountMulticallStorageAddress = + _getCreate2Address(address(accountMulticallFactory), uint256(salt), multicallStorageBytecode); + + bytes memory multicallExtensionBytecode = + abi.encodePacked(type(LlamaAccountMulticallExtension).creationCode, abi.encode(accountMulticallStorageAddress)); + accountMulticallExtensionAddress = + _getCreate2Address(address(accountMulticallFactory), uint256(salt), multicallExtensionBytecode); + + bytes memory multicallGuardBytecode = + abi.encodePacked(type(LlamaAccountMulticallGuard).creationCode, abi.encode(accountMulticallExtensionAddress)); + accountMulticallGuardAddress = + _getCreate2Address(address(accountMulticallFactory), uint256(salt), multicallGuardBytecode); + } } contract DeployAccountMulticallModule is LlamaAccountMulticallFactoryTest { function test_Deploy() public { LlamaAccountMulticallStorage.TargetSelectorAuthorization[] memory data = new LlamaAccountMulticallStorage.TargetSelectorAuthorization[](1); + data[0] = LlamaAccountMulticallStorage.TargetSelectorAuthorization( + address(0xdeadbeef), bytes4(keccak256("withdraw()")), true + ); LlamaAccountMulticallFactory.LlamaAccountMulticallConfig memory config = LlamaAccountMulticallFactory.LlamaAccountMulticallConfig(address(EXECUTOR), 0, data); - // vm.expectEmit(); - // emit LlamaAccountMulticallModuleCreated( - // address(this), address(EXECUTOR), 0, address(0), address(0), address(0), block.chainid - // ); + ( + address accountMulticallGuardAddress, + address accountMulticallExtensionAddress, + address accountMulticallStorageAddress + ) = _preComputeMulticallAddresses(address(this), 0); + + vm.expectEmit(); + emit LlamaAccountMulticallModuleCreated( + address(this), + address(EXECUTOR), + 0, + accountMulticallGuardAddress, + accountMulticallExtensionAddress, + accountMulticallStorageAddress, + block.chainid + ); ( LlamaAccountMulticallGuard accountMulticallGuard, LlamaAccountMulticallExtension accountMulticallExtension, @@ -51,5 +104,6 @@ contract DeployAccountMulticallModule is LlamaAccountMulticallFactoryTest { assertEq(address(accountMulticallGuard.ACCOUNT_MULTICALL_EXTENSION()), address(accountMulticallExtension)); assertEq(address(accountMulticallExtension.ACCOUNT_MULTICALL_STORAGE()), address(accountMulticallStorage)); assertEq(address(accountMulticallStorage.LLAMA_EXECUTOR()), address(EXECUTOR)); + assertTrue(accountMulticallStorage.authorizedTargetSelectors(address(0xdeadbeef), bytes4(keccak256("withdraw()")))); } } From fa36ff43ca24b5e8c561eca899c5cd16c24c27a2 Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Fri, 12 Apr 2024 17:09:18 -0400 Subject: [PATCH 38/40] test_RevertIf_SameDeployerExecutorNonce --- .../LlamaAccountMulticallFactory.t.sol | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/account-multicall/LlamaAccountMulticallFactory.t.sol b/test/account-multicall/LlamaAccountMulticallFactory.t.sol index 4fe5014..20cfb14 100644 --- a/test/account-multicall/LlamaAccountMulticallFactory.t.sol +++ b/test/account-multicall/LlamaAccountMulticallFactory.t.sol @@ -106,4 +106,25 @@ contract DeployAccountMulticallModule is LlamaAccountMulticallFactoryTest { assertEq(address(accountMulticallStorage.LLAMA_EXECUTOR()), address(EXECUTOR)); assertTrue(accountMulticallStorage.authorizedTargetSelectors(address(0xdeadbeef), bytes4(keccak256("withdraw()")))); } + + function test_RevertIf_SameDeployerExecutorNonce() public { + LlamaAccountMulticallStorage.TargetSelectorAuthorization[] memory data1 = + new LlamaAccountMulticallStorage.TargetSelectorAuthorization[](1); + data1[0] = LlamaAccountMulticallStorage.TargetSelectorAuthorization( + address(0xdeadbeef), bytes4(keccak256("withdraw()")), true + ); + LlamaAccountMulticallFactory.LlamaAccountMulticallConfig memory config = + LlamaAccountMulticallFactory.LlamaAccountMulticallConfig(address(EXECUTOR), 0, data1); + + // Initial deploy + accountMulticallFactory.deploy(config); + + LlamaAccountMulticallStorage.TargetSelectorAuthorization[] memory data2 = + new LlamaAccountMulticallStorage.TargetSelectorAuthorization[](0); + config = LlamaAccountMulticallFactory.LlamaAccountMulticallConfig(address(EXECUTOR), 0, data2); + + // Revert on same deployer, executor, and nonce. Even if the TargetSelectorAuthorization data is different. + vm.expectRevert(); + accountMulticallFactory.deploy(config); + } } From 131119322645673b6ce9090ea2b1436c78841fdc Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Fri, 12 Apr 2024 17:33:40 -0400 Subject: [PATCH 39/40] slow --- justfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/justfile b/justfile index f8e6cff..4d260d1 100644 --- a/justfile +++ b/justfile @@ -44,5 +44,5 @@ run-deploy-account-multicall-module-script flags: (run-script 'DeployLlamaAccoun dry-run-deploy-account-multicall-module: (run-deploy-account-multicall-module-script '') -deploy-account-multicall-module: (run-deploy-account-multicall-module-script '--broadcast --verify') +deploy-account-multicall-module: (run-deploy-account-multicall-module-script '--broadcast --verify --slow') From 681bf1f43330c1098e1d2c25e90fbc907f2d140e Mon Sep 17 00:00:00 2001 From: Rajath Alex Date: Mon, 15 Apr 2024 14:40:48 -0400 Subject: [PATCH 40/40] PR comments --- justfile | 4 ++-- src/account-multicall/LlamaAccountMulticallExtension.sol | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/justfile b/justfile index 4d260d1..09ab40e 100644 --- a/justfile +++ b/justfile @@ -34,9 +34,9 @@ deploy-voting-module: (run-deploy-voting-module-script '--broadcast --verify') # Account multicall module -dry-run-deploy-acount-multicall-factory: (run-script 'DeployLlamaAccountMulticallFactory') +dry-run-deploy-account-multicall-factory: (run-script 'DeployLlamaAccountMulticallFactory') -deploy-acount-multicall-factory: (run-script 'DeployLlamaAccountMulticallFactory' '--broadcast --verify --slow --build-info --build-info-path build_info') +deploy-account-multicall-factory: (run-script 'DeployLlamaAccountMulticallFactory' '--broadcast --verify --slow --build-info --build-info-path build_info') verify-account-multicall-factory: (run-script 'DeployLlamaAccountMulticallFactory' '--verify --resume') diff --git a/src/account-multicall/LlamaAccountMulticallExtension.sol b/src/account-multicall/LlamaAccountMulticallExtension.sol index 350095c..92ea291 100644 --- a/src/account-multicall/LlamaAccountMulticallExtension.sol +++ b/src/account-multicall/LlamaAccountMulticallExtension.sol @@ -8,7 +8,7 @@ import {LlamaUtils} from "src/lib/LlamaUtils.sol"; /// @title Llama Account Multicall Extension /// @author Llama (devsdosomething@llama.xyz) /// @notice An account extension that can multicall on behalf of the Llama account. -/// @dev This contract should be delegate-called from a Llama account. +/// @dev This contract should be delegatecalled from a Llama account. contract LlamaAccountMulticallExtension is LlamaBaseAccountExtension { /// @dev Struct to hold target data. struct TargetData {