Skip to content

Commit c5f84b7

Browse files
committed
Add a test for checking return data in execTransactionFromModuleReturnData
1 parent fec4b80 commit c5f84b7

File tree

2 files changed

+58
-15
lines changed

2 files changed

+58
-15
lines changed

contracts/base/ModuleManager.sol

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,29 @@ abstract contract ModuleManager is SelfAuthorized, Executor, GuardManager {
4040
}
4141
}
4242

43+
function runPreExecutionChecks(
44+
address to,
45+
uint256 value,
46+
bytes memory data,
47+
Enum.Operation operation,
48+
address guard
49+
) internal returns (bytes32 guardHash) {
50+
// Only whitelisted modules are allowed.
51+
require(msg.sender != SENTINEL_MODULES && modules[msg.sender] != address(0), "GS104");
52+
53+
if (guard != address(0)) {
54+
guardHash = Guard(guard).checkModuleTransaction(to, value, data, operation, msg.sender);
55+
}
56+
}
57+
58+
function postExecutionChecksWithEventEmissions(bytes32 guardHash, bool success, address guard) internal {
59+
if (guard != address(0)) {
60+
Guard(guard).checkAfterExecution(guardHash, success);
61+
}
62+
if (success) emit ExecutionFromModuleSuccess(msg.sender);
63+
else emit ExecutionFromModuleFailure(msg.sender);
64+
}
65+
4366
/**
4467
* @notice Enables the module `module` for the Safe.
4568
* @dev This can only be done via a Safe transaction.
@@ -85,22 +108,10 @@ abstract contract ModuleManager is SelfAuthorized, Executor, GuardManager {
85108
bytes memory data,
86109
Enum.Operation operation
87110
) public virtual returns (bool success) {
88-
// Only whitelisted modules are allowed.
89-
require(msg.sender != SENTINEL_MODULES && modules[msg.sender] != address(0), "GS104");
90-
// Execute transaction without further confirmations.
91111
address guard = getGuard();
92-
93-
bytes32 guardHash;
94-
if (guard != address(0)) {
95-
guardHash = Guard(guard).checkModuleTransaction(to, value, data, operation, msg.sender);
96-
}
112+
bytes32 guardHash = runPreExecutionChecks(to, value, data, operation, guard);
97113
success = execute(to, value, data, operation, type(uint256).max);
98-
99-
if (guard != address(0)) {
100-
Guard(guard).checkAfterExecution(guardHash, success);
101-
}
102-
if (success) emit ExecutionFromModuleSuccess(msg.sender);
103-
else emit ExecutionFromModuleFailure(msg.sender);
114+
postExecutionChecksWithEventEmissions(guardHash, success, guard);
104115
}
105116

106117
/**
@@ -118,7 +129,9 @@ abstract contract ModuleManager is SelfAuthorized, Executor, GuardManager {
118129
bytes memory data,
119130
Enum.Operation operation
120131
) public returns (bool success, bytes memory returnData) {
121-
success = execTransactionFromModule(to, value, data, operation);
132+
address guard = getGuard();
133+
bytes32 guardHash = runPreExecutionChecks(to, value, data, operation, guard);
134+
success = execute(to, value, data, operation, type(uint256).max);
122135
/* solhint-disable no-inline-assembly */
123136
/// @solidity memory-safe-assembly
124137
assembly {
@@ -135,6 +148,7 @@ abstract contract ModuleManager is SelfAuthorized, Executor, GuardManager {
135148
returnData := ptr
136149
}
137150
/* solhint-enable no-inline-assembly */
151+
postExecutionChecksWithEventEmissions(guardHash, success, guard);
138152
}
139153

140154
/**

test/core/Safe.GuardManager.spec.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,35 @@ describe("GuardManager", () => {
298298
});
299299

300300
describe("execTransactionFromModuleReturnData", () => {
301+
it("correctly returns the return data if the guard allows the transaction", async () => {
302+
const {
303+
safe,
304+
validGuardMock,
305+
signers: [user1, user2],
306+
} = await setupWithTemplate();
307+
const validGuardMockAddress = await validGuardMock.getAddress();
308+
await executeContractCallWithSigners(safe, safe, "enableModule", [user1.address], [user2]);
309+
310+
const mock = await getMock();
311+
const callData = "0xbeef73";
312+
const returnBytes32 = "0x" + crypto.randomBytes(32).toString("hex");
313+
await mock.givenCalldataReturnBytes32(callData, returnBytes32);
314+
const guardInterface = (await hre.ethers.getContractAt("Guard", validGuardMockAddress)).interface;
315+
const checkModuleTxData = guardInterface.encodeFunctionData("checkModuleTransaction", [
316+
await mock.getAddress(),
317+
0,
318+
callData,
319+
0,
320+
user1.address,
321+
]);
322+
323+
await validGuardMock.givenCalldataReturnBool(checkModuleTxData, true);
324+
325+
const returnData = await safe.execTransactionFromModuleReturnData.staticCall(await mock.getAddress(), 0, callData, 0);
326+
327+
expect(returnData[1]).to.equal(returnBytes32);
328+
});
329+
301330
it("reverts if the pre hook of the guard reverts", async () => {
302331
const {
303332
safe,

0 commit comments

Comments
 (0)