Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Registry checks for hooks #67

Merged
merged 47 commits into from
Aug 22, 2023
Merged
Show file tree
Hide file tree
Changes from 44 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
f7aadcf
[#46] Implement Guard interface in SafeProtocolManager
akshay-ap Jul 26, 2023
175d0b9
[#47] Create FunctionHandlerManager.sol and inherit in SafeProtocolMa…
akshay-ap Jul 27, 2023
ef92c98
[#47] Create BaseManager contract, rename modifier, rename error, che…
akshay-ap Jul 27, 2023
5a52eef
[#47] Update natspec doc
akshay-ap Jul 27, 2023
2d4a1a1
Merge branch 'main' of github.com:5afe/safe-protocol into feature-46-…
akshay-ap Aug 2, 2023
1692fe0
[#46] Setup Safe
akshay-ap Aug 2, 2023
579ef82
Merge branch 'main' of github.com:5afe/safe-protocol into feature-46-…
akshay-ap Aug 2, 2023
1d73328
[#46] Fix EOF
akshay-ap Aug 2, 2023
89fba60
[#46] User setupTest function
akshay-ap Aug 2, 2023
6beead3
[#46] Fix lint issue
akshay-ap Aug 2, 2023
4ceb3e3
[#46] Add tests
akshay-ap Aug 2, 2023
69ed0db
[#46] Add test with delegateCall for hooks flow
akshay-ap Aug 3, 2023
3c2e2d6
[#46] User temporary variable for storing hooks address
akshay-ap Aug 3, 2023
6624509
Merge branch 'feature-46-hooks-for-multisig-flow' of github.com:5afe/…
akshay-ap Aug 6, 2023
69e227c
[#47] Implement logic for non-static calls to function handler manage…
akshay-ap Aug 6, 2023
8bf6053
[#47] Add tests for Function Handler
akshay-ap Aug 7, 2023
446084d
Merge branch 'main' of github.com:5afe/safe-protocol into feature-47-…
akshay-ap Aug 7, 2023
4a05518
[#47] Pass sender address in handle function
akshay-ap Aug 7, 2023
77d0b54
[#47] Fix test
akshay-ap Aug 9, 2023
b406518
[#47] Use ZeroAddress from ethers
akshay-ap Aug 9, 2023
55ef824
[#46] Reset tempHooksAddress
akshay-ap Aug 9, 2023
675fe12
[#47] Test static call to function handler
akshay-ap Aug 9, 2023
0af2503
[#47] Fix lint issue
akshay-ap Aug 9, 2023
6e98d70
Merge branch 'feature-46-hooks-for-multisig-flow' of github.com:5afe/…
akshay-ap Aug 9, 2023
842c2ad
[#47] Fix typo
akshay-ap Aug 9, 2023
f826f98
[#46] Refactor tests for SafeProtocolManager as Guard
akshay-ap Aug 14, 2023
6eb0388
Merge branch 'main' of github.com:5afe/safe-protocol into feature-46-…
akshay-ap Aug 14, 2023
98c24cc
[#46] Fix failing test
akshay-ap Aug 14, 2023
08ee3de
Merge branch 'feature-46-hooks-for-multisig-flow' of github.com:5afe/…
akshay-ap Aug 15, 2023
dc14c0d
[#46] Update comment
akshay-ap Aug 15, 2023
dda6733
Merge branch 'feature-46-hooks-for-multisig-flow' of github.com:5afe/…
akshay-ap Aug 15, 2023
cc7513c
[#47] Update tests for Function Handler
akshay-ap Aug 15, 2023
1d749ec
[#47] Update tests for function handler
akshay-ap Aug 15, 2023
68705f1
[#47] Remove test function handler from .solcover.js
akshay-ap Aug 15, 2023
eecbd26
[#47] Return data from handle function
akshay-ap Aug 15, 2023
4029549
[#47] Verify call data passed to handle(...)
akshay-ap Aug 15, 2023
707d39e
[#47] Update doc string
akshay-ap Aug 16, 2023
c262612
[#47] Check if function handler is whitelisted
akshay-ap Aug 16, 2023
28d5645
[#47] Make fallback function non-payable, optimize codesize
akshay-ap Aug 16, 2023
9400aef
[#47] Fix lint issue
akshay-ap Aug 16, 2023
402e7a0
[#53] Registry checks for hooks
akshay-ap Aug 16, 2023
dc0209a
[#53] Remove chained assignments
akshay-ap Aug 21, 2023
5d3083e
Merge branch 'main' of github.com:5afe/safe-protocol into temp
akshay-ap Aug 22, 2023
ac21ff5
[#53] Fix failing tests
akshay-ap Aug 22, 2023
88999a4
[#53] Remove chained assignments
akshay-ap Aug 22, 2023
d448cb3
[#53] Update comment on using temp hooks address
akshay-ap Aug 22, 2023
c5dbe31
[#53] Update data sent as parameter to pre-check hook in checkModuleT…
akshay-ap Aug 22, 2023
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
3 changes: 2 additions & 1 deletion contracts/SafeProtocolManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,8 @@ contract SafeProtocolManager is ISafeProtocolManager, RegistryManager, HooksMana
address msgSender
) external {
// Store hooks address in tempHooksAddress so that checkAfterExecution(...) and checkModuleTransaction(...) can access it.
address tempHooksAddressForSafe = tempHooksAddress[msg.sender] = enabledHooks[msg.sender];
tempHooksAddress[msg.sender] = enabledHooks[msg.sender];
address tempHooksAddressForSafe = enabledHooks[msg.sender];

if (tempHooksAddressForSafe == address(0)) return;
bytes memory executionMetadata = abi.encode(
Expand Down
13 changes: 7 additions & 6 deletions contracts/base/HooksManager.sol
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.18;
import {ISafeProtocolHooks} from "../interfaces/Integrations.sol";

import {RegistryManager} from "./RegistryManager.sol";
import {OnlyAccountCallable} from "./OnlyAccountCallable.sol";

contract HooksManager is OnlyAccountCallable {
abstract contract HooksManager is RegistryManager, OnlyAccountCallable {
mapping(address => address) public enabledHooks;

/// @notice This variable should store the address of the hooks contract whenever
Expand All @@ -14,9 +16,6 @@ contract HooksManager is OnlyAccountCallable {
// Events
event HooksChanged(address indexed safe, address indexed hooksAddress);

// Errors
error AddressDoesNotImplementHooksInterface(address hooksAddress);

/**
* @notice Returns the address of hooks for a Safe account provided as a fucntion parameter.
* Returns address(0) is no hooks are enabled.
Expand All @@ -32,8 +31,10 @@ contract HooksManager is OnlyAccountCallable {
* @param hooks Address of the hooks to be enabled for msg.sender.
*/
function setHooks(address hooks) external onlyAccount {
if (hooks != address(0) && !ISafeProtocolHooks(hooks).supportsInterface(type(ISafeProtocolHooks).interfaceId)) {
revert AddressDoesNotImplementHooksInterface(hooks);
if (hooks != address(0)) {
checkPermittedIntegration(hooks);
if (!ISafeProtocolHooks(hooks).supportsInterface(type(ISafeProtocolHooks).interfaceId))
revert AccountDoesNotImplementValidInterfaceId(hooks);
}
enabledHooks[msg.sender] = hooks;
emit HooksChanged(msg.sender, hooks);
Expand Down
1 change: 0 additions & 1 deletion contracts/test/TestExecutor.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.18;
import {ISafe} from "../interfaces/Accounts.sol";
import {MockContract} from "@safe-global/mock-contract/contracts/MockContract.sol";

contract TestExecutor is ISafe {
address public module;
Expand Down
10 changes: 10 additions & 0 deletions test/SafeProtocolManager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,7 @@ describe("SafeProtocolManager", async () => {
const { safeProtocolManager, safe, safeProtocolRegistry } = await loadFixture(deployContractsWithEnabledManagerFixture);
// Enable hooks on a safe
const hooks = await getHooksWithPassingChecks();
await safeProtocolRegistry.connect(owner).addIntegration(hooks.target, IntegrationType.Hooks);
const dataSetHooks = safeProtocolManager.interface.encodeFunctionData("setHooks", [await hooks.getAddress()]);
await safe.exec(safe.target, 0, dataSetHooks);

Expand Down Expand Up @@ -509,6 +510,7 @@ describe("SafeProtocolManager", async () => {
const { safeProtocolManager, safe, safeProtocolRegistry } = await loadFixture(deployContractsWithEnabledManagerFixture);
// Enable hooks on a safe
const hooks = await getHooksWithFailingPrechecks();
await safeProtocolRegistry.connect(owner).addIntegration(hooks.target, IntegrationType.Hooks);

const dataSetHooks = safeProtocolManager.interface.encodeFunctionData("setHooks", [await hooks.getAddress()]);
await safe.exec(safe.target, 0, dataSetHooks);
Expand All @@ -529,6 +531,7 @@ describe("SafeProtocolManager", async () => {
const { safeProtocolManager, safe, safeProtocolRegistry } = await loadFixture(deployContractsWithEnabledManagerFixture);
// Enable hooks on a safe
const hooks = await getHooksWithFailingPostCheck();
await safeProtocolRegistry.connect(owner).addIntegration(hooks.target, IntegrationType.Hooks);

const dataSetHooks = safeProtocolManager.interface.encodeFunctionData("setHooks", [await hooks.getAddress()]);
await safe.exec(safe.target, 0, dataSetHooks);
Expand Down Expand Up @@ -671,8 +674,11 @@ describe("SafeProtocolManager", async () => {
it("Should execute a transaction from root access enabled plugin with hooks enabled", async () => {
const { safeProtocolManager, safe, safeProtocolRegistry } = await loadFixture(deployContractsWithEnabledManagerFixture);
const safeAddress = await safe.getAddress();

// Enable hooks on a safe
const hooks = await getHooksWithPassingChecks();
await safeProtocolRegistry.connect(owner).addIntegration(hooks.target, IntegrationType.Hooks);

const dataSetHooks = safeProtocolManager.interface.encodeFunctionData("setHooks", [await hooks.getAddress()]);
await safe.exec(safe.target, 0, dataSetHooks);

Expand Down Expand Up @@ -716,6 +722,7 @@ describe("SafeProtocolManager", async () => {
const { safeProtocolManager, safe, safeProtocolRegistry } = await loadFixture(deployContractsWithEnabledManagerFixture);
// Enable hooks on a safe
const hooks = await getHooksWithFailingPrechecks();
await safeProtocolRegistry.connect(owner).addIntegration(hooks.target, IntegrationType.Hooks);

const dataSetHooks = safeProtocolManager.interface.encodeFunctionData("setHooks", [await hooks.getAddress()]);
await safe.exec(safe.target, 0, dataSetHooks);
Expand Down Expand Up @@ -748,6 +755,8 @@ describe("SafeProtocolManager", async () => {

// Enable hooks on a safe
const hooks = await getHooksWithFailingPostCheck();
await safeProtocolRegistry.connect(owner).addIntegration(hooks.target, IntegrationType.Hooks);

const dataSetHooks = safeProtocolManager.interface.encodeFunctionData("setHooks", [await hooks.getAddress()]);
await safe.exec(safe.target, 0, dataSetHooks);

Expand Down Expand Up @@ -928,6 +937,7 @@ describe("SafeProtocolManager", async () => {

await safeProtocolRegistry.connect(owner).addIntegration(hooks.target, IntegrationType.Hooks);
await safeProtocolRegistry.connect(owner).addIntegration(hooksWithFailingPreChecks.target, IntegrationType.Hooks);
await safeProtocolRegistry.connect(owner).addIntegration(hooksWithFailingPostCheck.target, IntegrationType.Hooks);

return { safe, safeProtocolManager, hooks, hooksWithFailingPreChecks, hooksWithFailingPostCheck };
});
Expand Down
14 changes: 9 additions & 5 deletions test/base/HooksManager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { expect } from "chai";
import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers";
import { getHooksWithPassingChecks } from "../utils/mockHooksBuilder";
import { ZeroAddress } from "ethers";
import { IntegrationType } from "../utils/constants";

describe("HooksManager", async () => {
let deployer: SignerWithAddress, user1: SignerWithAddress, owner: SignerWithAddress;
Expand All @@ -21,8 +22,9 @@ describe("HooksManager", async () => {

const safe = await hre.ethers.deployContract("TestExecutor", [hooksManager.target], { signer: deployer });
const hooks = await getHooksWithPassingChecks();
await safeProtocolRegistry.connect(owner).addIntegration(hooks.target, IntegrationType.Hooks);

return { hooksManager, hooks, safe };
return { hooksManager, hooks, safe, safeProtocolRegistry };
});

it("Should emit HooksChanged event when hooks are enabled", async () => {
Expand Down Expand Up @@ -65,15 +67,17 @@ describe("HooksManager", async () => {
await expect(hooksManager.setHooks(hooksAddress)).to.be.reverted;
});

it("Should revert AddressDoesNotImplementHooksInterface if user attempts address does not implement Hooks interface", async () => {
const { hooksManager, safe } = await setupTests();
it("Should revert AccountDoesNotImplementValidInterfaceId if user attempts address does not implement Hooks interface", async () => {
const { hooksManager, safe, safeProtocolRegistry } = await setupTests();
const contractNotImplementingHooksInterface = await (await hre.ethers.getContractFactory("MockContract")).deploy();
await contractNotImplementingHooksInterface.givenMethodReturnBool("0x01ffc9a7", false);
await contractNotImplementingHooksInterface.givenMethodReturnBool("0x01ffc9a7", true);
await safeProtocolRegistry.connect(owner).addIntegration(contractNotImplementingHooksInterface.target, IntegrationType.Hooks);

await contractNotImplementingHooksInterface.givenMethodReturnBool("0x01ffc9a7", false);
const calldata = hooksManager.interface.encodeFunctionData("setHooks", [contractNotImplementingHooksInterface.target]);
await expect(safe.exec(safe.target, 0n, calldata)).to.be.revertedWithCustomError(
hooksManager,
"AddressDoesNotImplementHooksInterface",
"AccountDoesNotImplementValidInterfaceId",
);
});
});