Skip to content
This repository has been archived by the owner on Nov 27, 2024. It is now read-only.

feat: account multicall module #100

Merged
merged 40 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
9707b9e
rewards claim guard
0xrajath Apr 3, 2024
442abb1
natspec
0xrajath Apr 3, 2024
cd889cf
sort of working
0xrajath Apr 4, 2024
34b0ffe
sort of working
0xrajath Apr 4, 2024
2e74c52
working
0xrajath Apr 4, 2024
5eb0198
description
0xrajath Apr 4, 2024
77acfa0
initial script
0xrajath Apr 4, 2024
518cc1b
script input
0xrajath Apr 4, 2024
f81b30c
use checked increment
AustinGreen Apr 5, 2024
7f03607
fix
0xrajath Apr 5, 2024
881b648
isAuthorization true
0xrajath Apr 5, 2024
97b0642
test setup
0xrajath Apr 5, 2024
f865195
rewardsclaimstorage tests
0xrajath Apr 5, 2024
0a13c51
cleanup
0xrajath Apr 5, 2024
3b6f98b
mock rewards contract
0xrajath Apr 5, 2024
d3de0e9
setup
0xrajath Apr 5, 2024
9a84a22
setup
0xrajath Apr 5, 2024
df991cc
set guard
0xrajath Apr 5, 2024
1147e5f
fix
0xrajath Apr 5, 2024
5304828
account extension test
0xrajath Apr 6, 2024
8f75742
ILlamaAccount
0xrajath Apr 8, 2024
8eddba7
sort of working
0xrajath Apr 8, 2024
a4d9265
test_RevertIf_SelectorIsNotClaimRewards
0xrajath Apr 8, 2024
cf87865
test_RevertIf_NotDelegateCall
0xrajath Apr 8, 2024
58f0b3e
test_PositiveFlow
0xrajath Apr 8, 2024
fc602f7
refactor
0xrajath Apr 8, 2024
ff5c037
rewardsclaimaccountextension
0xrajath Apr 8, 2024
4e30027
test_ClaimRewards
0xrajath Apr 8, 2024
8731bae
test_RevertIf_NotAuthorizedTargetSelector
0xrajath Apr 9, 2024
fedf515
cleanup
0xrajath Apr 9, 2024
66b34bd
receive payable
0xrajath Apr 9, 2024
f10f468
test_RevertIf_NotDelegateCalled
0xrajath Apr 9, 2024
bdc4ed8
account multicall
0xrajath Apr 11, 2024
f8f6b7b
factory
0xrajath Apr 12, 2024
95581e1
InitializeAuthorizedTargetSelectors
0xrajath Apr 12, 2024
5d3d26d
initial factory test
0xrajath Apr 12, 2024
d0d8c36
factory deploy test
0xrajath Apr 12, 2024
fa36ff4
test_RevertIf_SameDeployerExecutorNonce
0xrajath Apr 12, 2024
1311193
slow
0xrajath Apr 12, 2024
681bf1f
PR comments
0xrajath Apr 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/common/LlamaBaseAccountExtension.sol
Original file line number Diff line number Diff line change
@@ -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);
}
}
59 changes: 59 additions & 0 deletions src/rewards-claimer/LlamaRewardsClaimAccountExtension.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// 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";

/// @title Llama Rewards Claim Account Extension
/// @author Llama ([email protected])
/// @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 is LlamaBaseAccountExtension {
/// @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 onlyDelegateCall returns (bytes[] memory returnData) {
0xrajath marked this conversation as resolved.
Show resolved Hide resolved
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)) {
0xrajath marked this conversation as resolved.
Show resolved Hide resolved
revert UnauthorizedTargetSelector(target, selector);
}

// Execute the call.
(bool success, bytes memory result) = target.call{value: value}(callData);
0xrajath marked this conversation as resolved.
Show resolved Hide resolved
if (!success) revert CallReverted(i, result);
returnData[i] = result;
}
}
}
43 changes: 43 additions & 0 deletions src/rewards-claimer/LlamaRewardsClaimGuard.sol
0xrajath marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: MIT
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 ([email protected])
/// @notice A guard that only allows the `RewardsClaimer.claimRewards` to be delegate-called
0xrajath marked this conversation as resolved.
Show resolved Hide resolved
/// @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` account extension.
LlamaRewardsClaimAccountExtension public immutable REWARDS_CLAIMER;

/// @dev Initializes the Llama rewards claim guard.
constructor(LlamaRewardsClaimAccountExtension rewardsClaimer) {
REWARDS_CLAIMER = 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, 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
function validatePreActionExecution(ActionInfo calldata actionInfo) external pure {}

/// @inheritdoc ILlamaActionGuard
function validatePostActionExecution(ActionInfo calldata actionInfo) external pure {}
}
51 changes: 51 additions & 0 deletions src/rewards-claimer/LlamaRewardsClaimStorage.sol
Original file line number Diff line number Diff line change
@@ -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 ([email protected])
/// @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);
}
}
}
Loading