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 all 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
21 changes: 19 additions & 2 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +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')

verify: (run-script 'DeployLlamaTokenVotingFactory' '--verify --resume')
# Account multicall module

dry-run-deploy-account-multicall-factory: (run-script 'DeployLlamaAccountMulticallFactory')

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')

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 --slow')

21 changes: 21 additions & 0 deletions script/DeployLlamaAccountMulticallFactory.s.sol
Original file line number Diff line number Diff line change
@@ -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))));
}
}
74 changes: 74 additions & 0 deletions script/DeployLlamaAccountMulticallModule.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// 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 {LlamaAccountMulticallFactory} from "src/account-multicall/LlamaAccountMulticallFactory.sol";
import {LlamaAccountMulticallGuard} from "src/account-multicall/LlamaAccountMulticallGuard.sol";
import {LlamaAccountMulticallStorage} from "src/account-multicall/LlamaAccountMulticallStorage.sol";

contract DeployLlamaAccountMulticallModule 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(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);

vm.broadcast(deployer);
(accountMulticallGuard, accountMulticallExtension, accountMulticallStorage) = factory.deploy(config);

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)))
);
DeployUtils.print(
string.concat(" LlamaAccountMulticallStorage: ", vm.toString(address(accountMulticallStorage)))
);
}

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;
}
}
13 changes: 13 additions & 0 deletions script/input/1/mockAccountMulticallConfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"comment": "This is an account multicall deployment for Forge tests.",
"factory": "0x90193C961A926261B756D1E5bb255e67ff9498A1",
"llamaExecutor": "0xdAf00E9786cABB195a8a1Cf102730863aE94Dd75",
"nonce": 0,
"initialTargetSelectorAuthorizations": [
{
"comment": "DeadBeef.withdraw()",
"selector": "0x3ccfd60b",
"target": "0x00000000000000000000000000000000deadbeef"
}
]
}
13 changes: 13 additions & 0 deletions script/input/11155111/accountMulticallConfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"comment": "This is an example account multicall deployment on Sepolia.",
"factory": "0x0000000000000000000000000000000000000000",
"llamaExecutor": "0x0000000000000000000000000000000000000000",
"nonce": 0,
"initialTargetSelectorAuthorizations": [
{
"comment": "Target::Selector",
"selector": "0x00000000",
"target": "0x0000000000000000000000000000000000000000"
}
]
}
59 changes: 59 additions & 0 deletions src/account-multicall/LlamaAccountMulticallExtension.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 {LlamaAccountMulticallStorage} from "src/account-multicall/LlamaAccountMulticallStorage.sol";
import {LlamaBaseAccountExtension} from "src/common/LlamaBaseAccountExtension.sol";
import {LlamaUtils} from "src/lib/LlamaUtils.sol";

/// @title Llama Account Multicall Extension
/// @author Llama ([email protected])
/// @notice An account extension that can multicall on behalf of the Llama account.
/// @dev This contract should be delegatecalled from a Llama account.
contract LlamaAccountMulticallExtension 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 Llama account multicall storage contract.
LlamaAccountMulticallStorage public immutable ACCOUNT_MULTICALL_STORAGE;

/// @dev Initializes the Llama account multicall extenstion.
constructor(LlamaAccountMulticallStorage accountMulticallStorage) {
ACCOUNT_MULTICALL_STORAGE = accountMulticallStorage;
}

/// @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 multicall(TargetData[] memory targetData) external onlyDelegateCall returns (bytes[] memory returnData) {
AustinGreen marked this conversation as resolved.
Show resolved Hide resolved
uint256 length = targetData.length;
returnData = new bytes[](length);
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;
bytes4 selector = bytes4(callData);

// Check if the target-selector is authorized.
if (!ACCOUNT_MULTICALL_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;
}
}
}
66 changes: 66 additions & 0 deletions src/account-multicall/LlamaAccountMulticallFactory.sol
0xrajath marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -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 ([email protected])
/// @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)
0xrajath marked this conversation as resolved.
Show resolved Hide resolved
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);
0xrajath marked this conversation as resolved.
Show resolved Hide resolved

// 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
);
}
}
44 changes: 44 additions & 0 deletions src/account-multicall/LlamaAccountMulticallGuard.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// 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";

/// @title Llama Account Multicall Guard
/// @author Llama ([email protected])
/// @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 LlamaAccountMulticallGuard is ILlamaActionGuard {
/// @dev Thrown if the call is not authorized.
error UnauthorizedCall(address target, bytes4 selector, bool withDelegatecall);

/// @notice The address of the Llama account multicall extension.
LlamaAccountMulticallExtension public immutable ACCOUNT_MULTICALL_EXTENSION;

/// @dev Initializes the Llama account multicall guard.
constructor(LlamaAccountMulticallExtension accountMulticallExtension) {
ACCOUNT_MULTICALL_EXTENSION = accountMulticallExtension;
}

/// @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 Llama account multicall extension, selector is `multicall` and the call type is a
// delegatecall.
if (
target != address(ACCOUNT_MULTICALL_EXTENSION) || selector != LlamaAccountMulticallExtension.multicall.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 {}
}
Loading
Loading