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

Commit

Permalink
account multicall
Browse files Browse the repository at this point in the history
  • Loading branch information
0xrajath committed Apr 11, 2024
1 parent f10f468 commit bdc4ed8
Show file tree
Hide file tree
Showing 14 changed files with 309 additions and 311 deletions.
6 changes: 3 additions & 3 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -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')
70 changes: 70 additions & 0 deletions script/DeployLlamaAccountMulticall.s.sol
Original file line number Diff line number Diff line change
@@ -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;
}
}
70 changes: 0 additions & 70 deletions script/DeployLlamaRewardsClaimer.s.sol

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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": [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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": [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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)) {
Expand All @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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
)
Expand All @@ -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);
}
}
Loading

0 comments on commit bdc4ed8

Please sign in to comment.