Skip to content

Commit

Permalink
[#47] Implement logic for non-static calls to function handler manage…
Browse files Browse the repository at this point in the history
…r, test to set function handler
  • Loading branch information
akshay-ap committed Aug 6, 2023
1 parent 6624509 commit 69e227c
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 9 deletions.
19 changes: 17 additions & 2 deletions contracts/base/FunctionHandlerManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
pragma solidity ^0.8.18;
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {BaseManager} from "./BaseManager.sol";
import {ISafeProtocolFunctionHandler} from "../interfaces/Integrations.sol";
import {ISafe} from "../interfaces/Accounts.sol";

/**
* @title FunctionHandlerManager
Expand All @@ -20,6 +22,7 @@ abstract contract FunctionHandlerManager is BaseManager {

// Errors
error AddressDoesNotImplementFunctionHandlerInterface(address functionHandler);
error FunctionHandlerNotSet(address safe, bytes4 functionSelector);

/**
* @notice Returns the function handler for a Safe account and function selector.
Expand All @@ -37,15 +40,27 @@ abstract contract FunctionHandlerManager is BaseManager {
* @param functionHandler Address of the contract to be set as a function handler
*/
function setFunctionHandler(bytes4 selector, address functionHandler) external onlyPermittedIntegration(functionHandler) {
if (functionHandler != address(0) && !IERC165(functionHandler).supportsInterface(0x00000000)) {
if (functionHandler != address(0) && !IERC165(functionHandler).supportsInterface(type(ISafeProtocolFunctionHandler).interfaceId)) {
revert AddressDoesNotImplementFunctionHandlerInterface(functionHandler);
}

functionHandlers[msg.sender][selector] = functionHandler;
emit FunctionHandlerChanged(msg.sender, selector, functionHandler);
}

fallback() external {
fallback() external payable {
address safe = msg.sender;
bytes4 functionSelector = bytes4(msg.data);

address functionHandler = functionHandlers[safe][functionSelector];

// Revert if functionHandler is not set
if (functionHandler == address(0)) {
revert FunctionHandlerNotSet(safe, functionSelector);
}

bytes memory data = ISafeProtocolFunctionHandler(functionHandler).handle(ISafe(safe), address(0), msg.value, msg.data);
}

receive() external payable {}
}
59 changes: 59 additions & 0 deletions test/FunctionHandlerManager.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers";
import hre, { deployments, ethers } from "hardhat";
import { getMockFunctionHandler } from "./utils/mockFunctionHandlerBuilder";
import { getSafeWithOwners } from "./utils/setup";
import { execTransaction } from "./utils/executeSafeTx";
import { IntegrationType } from "./utils/constants";
import { expect } from "chai";

describe("Test Function Handler", async () => {
let deployer: SignerWithAddress, owner: SignerWithAddress, user1: SignerWithAddress, user2: SignerWithAddress;

before(async () => {
[deployer, owner, user1, user2] = await hre.ethers.getSigners();
});

const setupTests = deployments.createFixture(async ({ deployments }) => {
await deployments.fixture();
[owner, user1] = await ethers.getSigners();
const safeProtocolRegistry = await ethers.deployContract("SafeProtocolRegistry", [owner.address], { signer: deployer });
const mockFunctionHandler = await getMockFunctionHandler();

// Can possibly use a test instance of FunctionHandlerManager instead of SafeProtocolManager.
// But, using SafeProtocolManager for testing with near production scenarios.
const functionHandlerManager = await (
await hre.ethers.getContractFactory("SafeProtocolManager")
).deploy(owner.address, await safeProtocolRegistry.getAddress());

await safeProtocolRegistry.addIntegration(mockFunctionHandler.target, IntegrationType.FunctionHandler);

const safe = await getSafeWithOwners([owner], 1, functionHandlerManager.target);

return { safe, functionHandlerManager, mockFunctionHandler };
});

it("Should emit FunctionHandlerChanged event when Function Handler is set", async () => {
const { safe, functionHandlerManager, mockFunctionHandler } = await setupTests();

// 0xf8a8fd6d -> function test() external {}
const dataSetFunctionHandler = functionHandlerManager.interface.encodeFunctionData("setFunctionHandler", [
"0xf8a8fd6d",
mockFunctionHandler.target,
]);

const tx = await execTransaction([owner], safe, functionHandlerManager, 0n, dataSetFunctionHandler, 0);
const receipt = await tx.wait();
const events = (
await functionHandlerManager.queryFilter(
functionHandlerManager.filters.FunctionHandlerChanged,
receipt?.blockNumber,
receipt?.blockNumber,
)
)[0];
expect(events.args).to.deep.equal([safe.target, "0xf8a8fd6d", mockFunctionHandler.target]);

expect(await functionHandlerManager.getFunctionHandler.staticCall(safe.target, "0xf8a8fd6d")).to.be.equal(
mockFunctionHandler.target,
);
});
});
4 changes: 2 additions & 2 deletions test/SafeProtocolRegistry.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers";
import { IntegrationType } from "./utils/constants";
import { getHooksWithPassingChecks, getHooksWithFailingCallToSupportsInterfaceMethod } from "./utils/mockHooksBuilder";
import { getPluginWithFailingCallToSupportsInterfaceMethod } from "./utils/mockPluginBuilder";
import { getFucntionHandlerWithFailingCallToSupportsInterfaceMethod } from "./utils/mockFunctionHandlerBuilder";
import { getFunctionHandlerWithFailingCallToSupportsInterfaceMethod } from "./utils/mockFunctionHandlerBuilder";
describe("SafeProtocolRegistry", async () => {
let owner: SignerWithAddress, user1: SignerWithAddress;

const setupTests = deployments.createFixture(async ({ deployments }) => {
await deployments.fixture();
[owner, user1] = await ethers.getSigners();
const safeProtocolRegistry = await ethers.deployContract("SafeProtocolRegistry", [owner.address]);
const mockFunctionHandlerAddress = (await getFucntionHandlerWithFailingCallToSupportsInterfaceMethod()).target;
const mockFunctionHandlerAddress = (await getFunctionHandlerWithFailingCallToSupportsInterfaceMethod()).target;
return { safeProtocolRegistry, mockFunctionHandlerAddress };
});

Expand Down
6 changes: 3 additions & 3 deletions test/utils/executeSafeTx.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers";
import { Safe } from "../../typechain-types";
import { AddressLike, BigNumberish } from "ethers";
import { AddressLike, BigNumberish, ContractTransactionResponse } from "ethers";
import hre from "hardhat";

export const execTransaction = async function (
Expand All @@ -10,7 +10,7 @@ export const execTransaction = async function (
value: BigNumberish,
data: string,
operation: number,
) {
): Promise<ContractTransactionResponse> {
const ADDRESS_0 = "0x0000000000000000000000000000000000000000";
const nonce = await safe.nonce();

Expand All @@ -27,5 +27,5 @@ export const execTransaction = async function (
signatureBytes += flatSig.slice(2);
}

await safe.execTransaction(to, value, data, operation, 0, 0, 0, ADDRESS_0, ADDRESS_0, signatureBytes);
return await safe.execTransaction(to, value, data, operation, 0, 0, 0, ADDRESS_0, ADDRESS_0, signatureBytes);
};
16 changes: 14 additions & 2 deletions test/utils/mockFunctionHandlerBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
import hre from "hardhat";
import { ISafeProtocolFunctionHandler } from "../../typechain-types";

export const getFucntionHandlerWithFailingCallToSupportsInterfaceMethod = async (): Promise<ISafeProtocolFunctionHandler> => {
export const getFunctionHandlerWithFailingCallToSupportsInterfaceMethod = async (): Promise<ISafeProtocolFunctionHandler> => {
const hooks = await (await hre.ethers.getContractFactory("MockContract")).deploy();

// 0x25d6803f -> type(ISafeProtocolFunctionHandler).interfaceId
await hooks.givenMethodReturnBool("0x25d6803f", false);
await hooks.givenMethodReturnBool("0x01ffc9a7", false);
return hre.ethers.getContractAt("ISafeProtocolFunctionHandler", hooks.target);
};

export const getMockFunctionHandler = async (): Promise<ISafeProtocolFunctionHandler> => {
const hooks = await (await hre.ethers.getContractFactory("MockContract")).deploy();

// 0x25d6803f -> type(ISafeProtocolFunctionHandler).interfaceId
await hooks.givenMethodReturnBool("0x01ffc9a7", true);

// 0xf8a8fd6d -> function test() external {}
await hooks.givenMethodReturnBool("0xf8a8fd6d", true);

return hre.ethers.getContractAt("ISafeProtocolFunctionHandler", hooks.target);
};

0 comments on commit 69e227c

Please sign in to comment.