Skip to content

Commit c8ef834

Browse files
authored
feat: emit logs (#56)
1 parent fd8f23f commit c8ef834

File tree

2 files changed

+300
-2
lines changed

2 files changed

+300
-2
lines changed

src/GovHelpers.sol

Lines changed: 287 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ pragma abicoder v2;
44

55
import {Vm} from 'forge-std/Vm.sol';
66
import {Test} from 'forge-std/Test.sol';
7+
import {console2} from 'forge-std/console2.sol';
78
import {AaveGovernanceV2, IAaveGovernanceV2, IExecutorWithTimelock} from 'aave-address-book/AaveGovernanceV2.sol';
89
import {IPoolAddressesProvider} from 'aave-address-book/AaveV3.sol';
910
import {AaveMisc} from 'aave-address-book/AaveMisc.sol';
@@ -112,6 +113,19 @@ library GovHelpers {
112113
withDelegatecalls[i] = true;
113114
}
114115

116+
console2.logBytes(
117+
abi.encodeWithSelector(
118+
AaveGovernanceV2.GOV.create.selector,
119+
IExecutorWithTimelock(executor),
120+
targets,
121+
values,
122+
signatures,
123+
calldatas,
124+
withDelegatecalls,
125+
ipfsHash
126+
)
127+
);
128+
115129
return
116130
AaveGovernanceV2.GOV.create(
117131
IExecutorWithTimelock(executor),
@@ -182,13 +196,285 @@ library GovHelpers {
182196

183197
/**
184198
* @dev Mock contract which allows performing a delegatecall to `execute`
185-
* Intended to be used as replacement for L2 admins to mock governance/gnosis execution.
199+
* Intended to be used as replacement for L2 admins/executors to mock governance/gnosis execution.
186200
*/
187201
contract MockExecutor {
202+
error OnlyQueuedActions();
203+
error InvalidActionsSetId();
204+
error InsufficientBalance();
205+
error FailedActionExecution();
206+
error DuplicateAction();
207+
error InconsistentParamsLength();
208+
error EmptyTargets();
209+
210+
/**
211+
* @dev Emitted when an ActionsSet is queued
212+
* @param id Id of the ActionsSet
213+
* @param targets Array of targets to be called by the actions set
214+
* @param values Array of values to pass in each call by the actions set
215+
* @param signatures Array of function signatures to encode in each call by the actions set
216+
* @param calldatas Array of calldata to pass in each call by the actions set
217+
* @param withDelegatecalls Array of whether to delegatecall for each call of the actions set
218+
* @param executionTime The timestamp at which this actions set can be executed
219+
**/
220+
event ActionsSetQueued(
221+
uint256 indexed id,
222+
address[] targets,
223+
uint256[] values,
224+
string[] signatures,
225+
bytes[] calldatas,
226+
bool[] withDelegatecalls,
227+
uint256 executionTime
228+
);
229+
230+
/**
231+
* @dev Emitted when an ActionsSet is successfully executed
232+
* @param id Id of the ActionsSet
233+
* @param initiatorExecution The address that triggered the ActionsSet execution
234+
* @param returnedData The returned data from the ActionsSet execution
235+
**/
236+
event ActionsSetExecuted(
237+
uint256 indexed id,
238+
address indexed initiatorExecution,
239+
bytes[] returnedData
240+
);
241+
242+
/**
243+
* @notice This struct contains the data needed to execute a specified set of actions
244+
* @param targets Array of targets to call
245+
* @param values Array of values to pass in each call
246+
* @param signatures Array of function signatures to encode in each call (can be empty)
247+
* @param calldatas Array of calldatas to pass in each call, appended to the signature at the same array index if not empty
248+
* @param withDelegateCalls Array of whether to delegatecall for each call
249+
* @param executionTime Timestamp starting from which the actions set can be executed
250+
* @param executed True if the actions set has been executed, false otherwise
251+
* @param canceled True if the actions set has been canceled, false otherwise
252+
*/
253+
struct ActionsSet {
254+
address[] targets;
255+
uint256[] values;
256+
string[] signatures;
257+
bytes[] calldatas;
258+
bool[] withDelegatecalls;
259+
uint256 executionTime;
260+
bool executed;
261+
bool canceled;
262+
}
263+
264+
/**
265+
* @notice This enum contains all possible actions set states
266+
*/
267+
enum ActionsSetState {
268+
Queued,
269+
Executed,
270+
Canceled,
271+
Expired
272+
}
273+
274+
// Time between queuing and execution
275+
uint256 private _delay;
276+
// Time after the execution time during which the actions set can be executed
277+
uint256 private _gracePeriod;
278+
// Minimum allowed delay
279+
uint256 private _minimumDelay;
280+
// Maximum allowed delay
281+
uint256 private _maximumDelay;
282+
// Address with the ability of canceling actions sets
283+
address private _guardian;
284+
285+
// Number of actions sets
286+
uint256 private _actionsSetCounter;
287+
// Map of registered actions sets (id => ActionsSet)
288+
mapping(uint256 => ActionsSet) private _actionsSets;
289+
// Map of queued actions (actionHash => isQueued)
290+
mapping(bytes32 => bool) private _queuedActions;
291+
292+
function execute(uint256 actionsSetId) external payable {
293+
if (getCurrentState(actionsSetId) != ActionsSetState.Queued) revert OnlyQueuedActions();
294+
295+
ActionsSet storage actionsSet = _actionsSets[actionsSetId];
296+
actionsSet.executed = true;
297+
uint256 actionCount = actionsSet.targets.length;
298+
299+
bytes[] memory returnedData = new bytes[](actionCount);
300+
for (uint256 i = 0; i < actionCount; ) {
301+
returnedData[i] = _executeTransaction(
302+
actionsSet.targets[i],
303+
actionsSet.values[i],
304+
actionsSet.signatures[i],
305+
actionsSet.calldatas[i],
306+
actionsSet.executionTime,
307+
actionsSet.withDelegatecalls[i]
308+
);
309+
unchecked {
310+
++i;
311+
}
312+
}
313+
314+
emit ActionsSetExecuted(actionsSetId, msg.sender, returnedData);
315+
}
316+
317+
/**
318+
* @notice Non-standard functionality used to skip governance and just execute a payload.
319+
*/
188320
function execute(address payload) public {
189321
(bool success, ) = address(payload).delegatecall(abi.encodeWithSignature('execute()'));
190322
require(success, 'PROPOSAL_EXECUTION_FAILED');
191323
}
324+
325+
/**
326+
* @notice Queue an ActionsSet
327+
* @dev If a signature is empty, calldata is used for the execution, calldata is appended to signature otherwise
328+
* @param targets Array of targets to be called by the actions set
329+
* @param values Array of values to pass in each call by the actions set
330+
* @param signatures Array of function signatures to encode in each call (can be empty)
331+
* @param calldatas Array of calldata to pass in each call (can be empty)
332+
* @param withDelegatecalls Array of whether to delegatecall for each call
333+
**/
334+
function queue(
335+
address[] memory targets,
336+
uint256[] memory values,
337+
string[] memory signatures,
338+
bytes[] memory calldatas,
339+
bool[] memory withDelegatecalls
340+
) public {
341+
if (targets.length == 0) revert EmptyTargets();
342+
uint256 targetsLength = targets.length;
343+
if (
344+
targetsLength != values.length ||
345+
targetsLength != signatures.length ||
346+
targetsLength != calldatas.length ||
347+
targetsLength != withDelegatecalls.length
348+
) revert InconsistentParamsLength();
349+
350+
uint256 actionsSetId = _actionsSetCounter;
351+
uint256 executionTime = block.timestamp + _delay;
352+
unchecked {
353+
++_actionsSetCounter;
354+
}
355+
356+
for (uint256 i = 0; i < targetsLength; ) {
357+
bytes32 actionHash = keccak256(
358+
abi.encode(
359+
targets[i],
360+
values[i],
361+
signatures[i],
362+
calldatas[i],
363+
executionTime,
364+
withDelegatecalls[i]
365+
)
366+
);
367+
if (isActionQueued(actionHash)) revert DuplicateAction();
368+
_queuedActions[actionHash] = true;
369+
unchecked {
370+
++i;
371+
}
372+
}
373+
374+
ActionsSet storage actionsSet = _actionsSets[actionsSetId];
375+
actionsSet.targets = targets;
376+
actionsSet.values = values;
377+
actionsSet.signatures = signatures;
378+
actionsSet.calldatas = calldatas;
379+
actionsSet.withDelegatecalls = withDelegatecalls;
380+
actionsSet.executionTime = executionTime;
381+
382+
emit ActionsSetQueued(
383+
actionsSetId,
384+
targets,
385+
values,
386+
signatures,
387+
calldatas,
388+
withDelegatecalls,
389+
executionTime
390+
);
391+
}
392+
393+
function getCurrentState(uint256 actionsSetId) public view returns (ActionsSetState) {
394+
if (_actionsSetCounter <= actionsSetId) revert InvalidActionsSetId();
395+
ActionsSet storage actionsSet = _actionsSets[actionsSetId];
396+
if (actionsSet.canceled) {
397+
return ActionsSetState.Canceled;
398+
} else if (actionsSet.executed) {
399+
return ActionsSetState.Executed;
400+
} else if (block.timestamp > actionsSet.executionTime + _gracePeriod) {
401+
return ActionsSetState.Expired;
402+
} else {
403+
return ActionsSetState.Queued;
404+
}
405+
}
406+
407+
function isActionQueued(bytes32 actionHash) public view returns (bool) {
408+
return _queuedActions[actionHash];
409+
}
410+
411+
function _executeTransaction(
412+
address target,
413+
uint256 value,
414+
string memory signature,
415+
bytes memory data,
416+
uint256 executionTime,
417+
bool withDelegatecall
418+
) internal returns (bytes memory) {
419+
if (address(this).balance < value) revert InsufficientBalance();
420+
421+
bytes32 actionHash = keccak256(
422+
abi.encode(target, value, signature, data, executionTime, withDelegatecall)
423+
);
424+
_queuedActions[actionHash] = false;
425+
426+
bytes memory callData;
427+
if (bytes(signature).length == 0) {
428+
callData = data;
429+
} else {
430+
callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data);
431+
}
432+
433+
bool success;
434+
bytes memory resultData;
435+
if (withDelegatecall) {
436+
(success, resultData) = this.executeDelegateCall{value: value}(target, callData);
437+
} else {
438+
// solium-disable-next-line security/no-call-value
439+
(success, resultData) = target.call{value: value}(callData);
440+
}
441+
return _verifyCallResult(success, resultData);
442+
}
443+
444+
function executeDelegateCall(address target, bytes calldata data)
445+
external
446+
payable
447+
returns (bool, bytes memory)
448+
{
449+
bool success;
450+
bytes memory resultData;
451+
// solium-disable-next-line security/no-call-value
452+
(success, resultData) = target.delegatecall(data);
453+
return (success, resultData);
454+
}
455+
456+
function _verifyCallResult(bool success, bytes memory returnData)
457+
private
458+
pure
459+
returns (bytes memory)
460+
{
461+
if (success) {
462+
return returnData;
463+
} else {
464+
// Look for revert reason and bubble it up if present
465+
if (returnData.length > 0) {
466+
// The easiest way to bubble the revert reason is using memory via assembly
467+
468+
// solhint-disable-next-line no-inline-assembly
469+
assembly {
470+
let returndata_size := mload(returnData)
471+
revert(add(32, returnData), returndata_size)
472+
}
473+
} else {
474+
revert FailedActionExecution();
475+
}
476+
}
477+
}
192478
}
193479

194480
/**

src/test/GovTest.t.sol

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
pragma solidity ^0.8.0;
33

44
import 'forge-std/Test.sol';
5-
import {GovHelpers} from '../GovHelpers.sol';
5+
import {GovHelpers, TestWithExecutor} from '../GovHelpers.sol';
66
import {AaveMisc} from 'aave-address-book/AaveMisc.sol';
7+
import {AaveGovernanceV2} from 'aave-address-book/AaveGovernanceV2.sol';
78

89
contract GovernanceTest is Test {
910
function setUp() public {
@@ -20,3 +21,14 @@ contract GovernanceTest is Test {
2021
vm.stopPrank();
2122
}
2223
}
24+
25+
contract GovernanceExistingProposalTest is TestWithExecutor {
26+
function setUp() public {
27+
vm.createSelectFork('polygon', 39582255);
28+
_selectPayloadExecutor(AaveGovernanceV2.POLYGON_BRIDGE_EXECUTOR);
29+
}
30+
31+
function testCreateProposal() public {
32+
_executor.execute(15);
33+
}
34+
}

0 commit comments

Comments
 (0)