-
-
Notifications
You must be signed in to change notification settings - Fork 112
Expand file tree
/
Copy pathSpecificActionERC20TransferBatchEnforcer.sol
More file actions
139 lines (114 loc) · 5.3 KB
/
Copy pathSpecificActionERC20TransferBatchEnforcer.sol
File metadata and controls
139 lines (114 loc) · 5.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
// SPDX-License-Identifier: MIT AND Apache-2.0
pragma solidity ^0.8.23;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { ExecutionLib } from "@erc7579/lib/ExecutionLib.sol";
import { ModeLib } from "@erc7579/lib/ModeLib.sol";
import { CaveatEnforcer } from "./CaveatEnforcer.sol";
import { ModeCode, Execution } from "../utils/Types.sol";
/**
* @title SpecificActionERC20TransferBatchEnforcer
* @dev This enforcer validates a batch of exactly 2 transactions where:
* 1. First transaction must match specific target, value, method and calldata
* 2. Second transaction must be an ERC20 transfer with specific parameters
* @dev The delegation can only be executed once
* @dev This enforcer operates only in batch execution call type and with default execution mode.
*/
contract SpecificActionERC20TransferBatchEnforcer is CaveatEnforcer {
using ExecutionLib for bytes;
using ModeLib for ModeCode;
////////////////////////////// State //////////////////////////////
// Tracks if a delegation has been executed
mapping(address delegationManager => mapping(bytes32 delegationHash => bool used)) public usedDelegations;
////////////////////////////// Events //////////////////////////////
event DelegationExecuted(address indexed delegationManager, bytes32 indexed delegationHash, address indexed delegator);
////////////////////////////// Structs //////////////////////////////
struct TermsData {
address tokenAddress;
address recipient;
uint256 amount;
address firstTarget;
uint256 firstValue;
bytes firstCalldata;
}
////////////////////////////// Public Methods //////////////////////////////
/**
* @notice Enforces the batch execution rules
* @param _terms The encoded terms containing:
* - ERC20 token address (20 bytes)
* - Transfer recipient address (20 bytes)
* - Transfer amount (32 bytes)
* - First transaction target address (20 bytes)
* - First transaction value (32 bytes)
* - First transaction calldata (remaining bytes)
* @param _mode The execution mode. (Must be Batch callType, Default execType)
* @param _executionCallData The batch execution calldata
* @param _delegationHash The delegation hash
*/
function beforeHook(
bytes calldata _terms,
bytes calldata,
ModeCode _mode,
bytes calldata _executionCallData,
bytes32 _delegationHash,
address _delegator,
address
)
public
override
onlyBatchCallTypeMode(_mode)
onlyDefaultExecutionMode(_mode)
{
// Check delegation hasn't been used
if (usedDelegations[msg.sender][_delegationHash]) {
revert("SpecificActionERC20TransferBatchEnforcer:delegation-already-used");
}
// Mark delegation as used
usedDelegations[msg.sender][_delegationHash] = true;
// Decode the batch executions
Execution[] calldata executions_ = _executionCallData.decodeBatch();
// Validate batch size
if (executions_.length != 2) {
revert("SpecificActionERC20TransferBatchEnforcer:invalid-batch-size");
}
// Decode terms into struct
TermsData memory terms_ = getTermsInfo(_terms);
// Validate first transaction
if (
executions_[0].target != terms_.firstTarget || executions_[0].value != terms_.firstValue
|| keccak256(executions_[0].callData) != keccak256(terms_.firstCalldata)
) {
revert("SpecificActionERC20TransferBatchEnforcer:invalid-first-transaction");
}
// Validate second transaction
if (
executions_[1].target != terms_.tokenAddress || executions_[1].value != 0 || executions_[1].callData.length != 68
|| bytes4(executions_[1].callData[0:4]) != IERC20.transfer.selector
|| address(uint160(uint256(bytes32(executions_[1].callData[4:36])))) != terms_.recipient
|| uint256(bytes32(executions_[1].callData[36:68])) != terms_.amount
) {
revert("SpecificActionERC20TransferBatchEnforcer:invalid-second-transaction");
}
emit DelegationExecuted(msg.sender, _delegationHash, _delegator);
}
/**
* @notice Decodes the terms used in this CaveatEnforcer
* @param _terms The encoded terms
* @return termsData_ The decoded terms data
*/
function getTermsInfo(bytes calldata _terms) public pure returns (TermsData memory termsData_) {
// Require minimum length: 20 + 20 + 32 + 20 + 32 = 124 bytes
require(_terms.length >= 124, "SpecificActionERC20TransferBatchEnforcer:invalid-terms-length");
// First 20 bytes is token address
termsData_.tokenAddress = address(bytes20(_terms[0:20]));
// Next 20 bytes is recipient address
termsData_.recipient = address(bytes20(_terms[20:40]));
// Next 32 bytes is amount
termsData_.amount = uint256(bytes32(_terms[40:72]));
// Next 20 bytes is first target
termsData_.firstTarget = address(bytes20(_terms[72:92]));
// Next 32 bytes is first value
termsData_.firstValue = uint256(bytes32(_terms[92:124]));
// Remaining bytes is firstCalldata
termsData_.firstCalldata = _terms[124:];
}
}