Skip to content

Commit

Permalink
wip shenanigans
Browse files Browse the repository at this point in the history
  • Loading branch information
mmv08 committed Sep 15, 2023
1 parent 8db0253 commit 6d911b9
Show file tree
Hide file tree
Showing 15 changed files with 190 additions and 120 deletions.
22 changes: 13 additions & 9 deletions contracts/contracts/Base.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.18;

import {ISafeProtocolPlugin} from "@safe-global/safe-core-protocol/contracts/interfaces/Integrations.sol";
import {ISafeProtocolPlugin} from "@safe-global/safe-core-protocol/contracts/interfaces/Modules.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";

enum MetadataProviderType {
Expand All @@ -18,7 +18,7 @@ interface IMetadataProvider {
struct PluginMetadata {
string name;
string version;
bool requiresRootAccess;
uint8 permissions;
string iconUrl;
string appUrl;
}
Expand All @@ -29,17 +29,17 @@ library PluginMetadataOps {
abi.encodePacked(
uint8(0x00), // Format
uint8(0x00), // Format version
abi.encode(data.name, data.version, data.requiresRootAccess, data.iconUrl, data.appUrl) // Plugin Metadata
abi.encode(data.name, data.version, data.permissions, data.iconUrl, data.appUrl) // Plugin Metadata
);
}

function decode(bytes calldata data) internal pure returns (PluginMetadata memory) {
require(bytes16(data[0:2]) == bytes16(0x0000), "Unsupported format or format version");
(string memory name, string memory version, bool requiresRootAccess, string memory iconUrl, string memory appUrl) = abi.decode(
(string memory name, string memory version, uint8 permissions, string memory iconUrl, string memory appUrl) = abi.decode(
data[2:],
(string, string, bool, string, string)
(string, string, uint8, string, string)
);
return PluginMetadata(name, version, requiresRootAccess, iconUrl, appUrl);
return PluginMetadata(name, version, permissions, iconUrl, appUrl);
}
}

Expand All @@ -48,19 +48,23 @@ abstract contract BasePlugin is ISafeProtocolPlugin {

string public name;
string public version;
bool public immutable requiresRootAccess;
uint8 public permissions;
bytes32 public immutable metadataHash;

constructor(PluginMetadata memory metadata) {
name = metadata.name;
version = metadata.version;
requiresRootAccess = metadata.requiresRootAccess;
permissions = metadata.permissions;
metadataHash = keccak256(metadata.encode());
}

function supportsInterface(bytes4 interfaceId) external pure override returns (bool) {
return interfaceId == type(ISafeProtocolPlugin).interfaceId || interfaceId == type(IERC165).interfaceId;
}

function requiresPermissions() external view virtual override returns (uint8) {
return permissions;
}
}

abstract contract BasePluginWithStoredMetadata is BasePlugin, IMetadataProvider {
Expand Down Expand Up @@ -92,7 +96,7 @@ abstract contract BasePluginWithEventMetadata is BasePlugin {
emit Metadata(metadataHash, metadata.encode());
}

function metadataProvider() public view override returns (uint256 providerType, bytes memory location) {
function metadataProvider() public view virtual override returns (uint256 providerType, bytes memory location) {
providerType = uint256(MetadataProviderType.Event);
location = abi.encode(address(this));
}
Expand Down
67 changes: 37 additions & 30 deletions contracts/contracts/ERC4337Plugin.sol
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.18;
import {ISafeProtocolPlugin} from "@safe-global/safe-core-protocol/contracts/interfaces/Integrations.sol";
import {ISafeProtocolPlugin, ISafeProtocolFunctionHandler} from "@safe-global/safe-core-protocol/contracts/interfaces/Modules.sol";

import {SafeTransaction, SafeRootAccess, SafeProtocolAction} from "@safe-global/safe-core-protocol/contracts/DataTypes.sol";
import {BasePluginWithEventMetadata, PluginMetadata} from "./Base.sol";
import {BasePluginWithEventMetadata, PluginMetadata, MetadataProviderType} from "./Base.sol";

interface ISafe {
function isOwner(address owner) external view returns (bool);
Expand All @@ -25,13 +25,13 @@ interface ISafe {
bytes memory data,
uint8 operation
) external returns (bool success, bytes memory returnData);
}

interface ISafeProtocolManager {
function enablePlugin(address plugin, bool allowRootAccess) external;

function setFunctionHandler(bytes4 selector, address functionHandler) external;
}

interface ISafeProtocolManager {
/**
* @notice This function allows enabled plugins to execute non-delegate call transactions thorugh a Safe.
* It should validate the status of the plugin through the registry and allows only listed and non-flagged integrations to execute transactions.
Expand Down Expand Up @@ -73,29 +73,22 @@ struct UserOperation {
* @notice This plugin does not need Safe owner(s) confirmation(s) to execute Safe txs once enabled
* through a Safe{Core} Protocol Manager.
*/
contract ERC4337Plugin is BasePluginWithEventMetadata {
contract ERC4337Plugin is ISafeProtocolFunctionHandler, BasePluginWithEventMetadata {
address public immutable PLUGIN_ADDRESS;
address public immutable SAFE_PROTOCOL_MANAGER;
ISafeProtocolManager public immutable SAFE_PROTOCOL_MANAGER;
address payable public immutable ENTRY_POINT;

constructor(
address safeCoreProtocolManager,
ISafeProtocolManager safeCoreProtocolManager,
address payable entryPoint
)
BasePluginWithEventMetadata(
PluginMetadata({name: "ERC4337 Plugin", version: "1.0.0", requiresRootAccess: false, iconUrl: "", appUrl: ""})
)
{
) BasePluginWithEventMetadata(PluginMetadata({name: "ERC4337 Plugin", version: "1.0.0", permissions: 1, iconUrl: "", appUrl: ""})) {
PLUGIN_ADDRESS = address(this);
SAFE_PROTOCOL_MANAGER = safeCoreProtocolManager;
ENTRY_POINT = entryPoint;
}

function validateUserOp(
UserOperation calldata userOp,
bytes32,
uint256 missingAccountFunds
) external onlyEntryPoint returns (uint256 validationData) {
function validateUserOp(UserOperation calldata userOp, bytes32, uint256 missingAccountFunds) external returns (uint256 validationData) {
require(msg.sender == address(PLUGIN_ADDRESS));
address payable safeAddress = payable(userOp.sender);
ISafe senderSafe = ISafe(safeAddress);

Expand All @@ -106,32 +99,46 @@ contract ERC4337Plugin is BasePluginWithEventMetadata {
return 0;
}

function execTransaction(address payable to, uint256 value, bytes calldata data) external payable onlyEntryPoint {
function execTransaction(address payable to, uint256 value, bytes calldata data) external {
require(msg.sender == address(PLUGIN_ADDRESS));
address payable safeAddress = payable(msg.sender);
ISafe safe = ISafe(safeAddress);
require(safe.execTransactionFromModule(to, value, data, 0), "tx failed");
}

function handle(address safe, address sender, uint256 value, bytes calldata data) external returns (bytes memory result) {
bytes4 selector = bytes4(data[0:4]);

if (selector == this.validateUserOp.selector) {
(, result) = PLUGIN_ADDRESS.call(data);
} else if (selector == this.execTransaction.selector) {
(, result) = PLUGIN_ADDRESS.call(data);
}
}

function enableSafeCoreProtocolWith4337Plugin() public {
require(address(this) != PLUGIN_ADDRESS, "Only delegatecall");

ISafeProtocolManager safeCoreProtocolManager = ISafeProtocolManager(SAFE_PROTOCOL_MANAGER);
ISafe safe = ISafe(address(this));

safe.enableModule(SAFE_PROTOCOL_MANAGER);
safe.setFallbackHandler(SAFE_PROTOCOL_MANAGER);

safeCoreProtocolManager.enablePlugin(PLUGIN_ADDRESS, false);
safeCoreProtocolManager.setFunctionHandler(this.validateUserOp.selector, PLUGIN_ADDRESS);
safeCoreProtocolManager.setFunctionHandler(this.execTransaction.selector, PLUGIN_ADDRESS);
safe.setFallbackHandler(address(SAFE_PROTOCOL_MANAGER));
safe.enableModule(address(SAFE_PROTOCOL_MANAGER));
safe.enablePlugin(PLUGIN_ADDRESS, false);
safe.setFunctionHandler(this.validateUserOp.selector, PLUGIN_ADDRESS);
safe.setFunctionHandler(this.execTransaction.selector, PLUGIN_ADDRESS);
}

function requireFromEntryPoint() internal view {
require(msg.sender == ENTRY_POINT, "Only entry point");
function requireFromEntryPoint(address sender) internal view {
require(sender == ENTRY_POINT, "Only entry point");
}

modifier onlyEntryPoint() {
requireFromEntryPoint();
_;
function metadataProvider()
public
view
override(BasePluginWithEventMetadata, ISafeProtocolFunctionHandler)
returns (uint256 providerType, bytes memory location)
{
providerType = uint256(MetadataProviderType.Contract);
location = abi.encode(address(this));
}
}
1 change: 1 addition & 0 deletions contracts/contracts/Imports.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma solidity ^0.8.18;
// Import the contract so hardhat compiles it, and we have the ABI available
import {MockContract} from "@safe-global/mock-contract/contracts/MockContract.sol";
import {TestSafeProtocolRegistryUnrestricted} from "@safe-global/safe-core-protocol/contracts/test/TestSafeProtocolRegistryUnrestricted.sol";
import {SafeProtocolManager} from "@safe-global/safe-core-protocol/contracts/SafeProtocolManager.sol";
import {Safe} from "@safe-global/safe-contracts/contracts/Safe.sol";
import {SafeProxyFactory} from "@safe-global/safe-contracts/contracts/proxies/SafeProxyFactory.sol";
import {SafeProxy} from "@safe-global/safe-contracts/contracts/proxies/SafeProxy.sol";
Expand Down
2 changes: 1 addition & 1 deletion contracts/contracts/Plugins.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ contract RelayPlugin is BasePluginWithEventMetadata {
PluginMetadata({
name: "Relay Plugin",
version: "1.0.0",
requiresRootAccess: false,
permissions: 1,
iconUrl: "",
appUrl: "https://5afe.github.io/safe-core-protocol-demo/#/relay/${plugin}"
})
Expand Down
7 changes: 1 addition & 6 deletions contracts/contracts/RecoveryWithDelayPlugin.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 "@safe-global/safe-core-protocol/contracts/interfaces/Accounts.sol";
import {ISafeProtocolPlugin} from "@safe-global/safe-core-protocol/contracts/interfaces/Integrations.sol";
import {ISafeProtocolManager} from "@safe-global/safe-core-protocol/contracts/interfaces/Manager.sol";
import {BasePluginWithEventMetadata, PluginMetadata} from "./Base.sol";
import {SafeTransaction, SafeRootAccess, SafeProtocolAction} from "@safe-global/safe-core-protocol/contracts/DataTypes.sol";
Expand Down Expand Up @@ -52,11 +51,7 @@ contract RecoveryWithDelayPlugin is BasePluginWithEventMetadata {

constructor(
address _recoverer
)
BasePluginWithEventMetadata(
PluginMetadata({name: "Recovery Plugin", version: "1.0.0", requiresRootAccess: true, iconUrl: "", appUrl: ""})
)
{
) BasePluginWithEventMetadata(PluginMetadata({name: "Recovery Plugin", version: "1.0.0", permissions: 2, iconUrl: "", appUrl: ""})) {
recoverer = _recoverer;
}

Expand Down
7 changes: 2 additions & 5 deletions contracts/contracts/WhitelistPlugin.sol
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.18;
import {ISafe} from "@safe-global/safe-core-protocol/contracts/interfaces/Accounts.sol";
import {ISafeProtocolPlugin} from "@safe-global/safe-core-protocol/contracts/interfaces/Integrations.sol";
import {ISafeProtocolManager} from "@safe-global/safe-core-protocol/contracts/interfaces/Manager.sol";
import {SafeTransaction, SafeRootAccess, SafeProtocolAction} from "@safe-global/safe-core-protocol/contracts/DataTypes.sol";
import {SafeTransaction, SafeProtocolAction} from "@safe-global/safe-core-protocol/contracts/DataTypes.sol";
import {BasePluginWithEventMetadata, PluginMetadata} from "./Base.sol";

/**
Expand Down Expand Up @@ -31,9 +30,7 @@ contract WhitelistPlugin is BasePluginWithEventMetadata {
error CallerIsNotOwner(address safe, address caller);

constructor()
BasePluginWithEventMetadata(
PluginMetadata({name: "Whitelist Plugin", version: "1.0.0", requiresRootAccess: false, iconUrl: "", appUrl: ""})
)
BasePluginWithEventMetadata(PluginMetadata({name: "Whitelist Plugin", version: "1.0.0", permissions: 1, iconUrl: "", appUrl: ""}))
{}

/**
Expand Down
4 changes: 2 additions & 2 deletions contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"@openzeppelin/contracts": "^4.9.3",
"@safe-global/mock-contract": "^4.1.0",
"@safe-global/safe-contracts": "^1.4.1-build.0",
"@safe-global/safe-core-protocol": "0.2.0-alpha.1",
"@safe-global/safe-core-protocol": "^0.2.1-alpha.0",
"@safe-global/safe-singleton-factory": "^1.0.15",
"@typechain/ethers-v6": "^0.5.0",
"@typechain/hardhat": "^9.0.0",
Expand Down Expand Up @@ -71,4 +71,4 @@
"test",
"artifacts"
]
}
}
34 changes: 16 additions & 18 deletions contracts/src/tasks/test_registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,21 @@ import { getPlugin, getRegistry, getRelayPlugin } from "../../test/utils/contrac
import { IntegrationType } from "../utils/constants";
import { loadPluginMetadata } from "../utils/metadata";

task("register-plugin", "Registers the sample Plugin in the Safe{Core} test register")
.setAction(async (_, hre) => {
const registry = await getRegistry(hre)
const plugin = await getRelayPlugin(hre)
await registry.addIntegration(await plugin.getAddress(), IntegrationType.Plugin)
console.log("Registered Plugin registry")
});
task("register-plugin", "Registers the sample Plugin in the Safe{Core} test register").setAction(async (_, hre) => {
const registry = await getRegistry(hre);
const plugin = await getRelayPlugin(hre);
await registry.addModule(await plugin.getAddress(), IntegrationType.Plugin);
console.log("Registered Plugin registry");
});

task("list-plugins", "List available Plugins in the Safe{Core} test register")
.setAction(async (_, hre) => {
const registry = await getRegistry(hre)
const events = await registry.queryFilter(registry.filters.IntegrationAdded)
for (const event of events) {
const plugin = await getPlugin(hre, event.args.integration)
const metadata = await loadPluginMetadata(hre, plugin)
console.log(event.args.integration, metadata)
}
});
task("list-plugins", "List available Plugins in the Safe{Core} test register").setAction(async (_, hre) => {
const registry = await getRegistry(hre);
const events = await registry.queryFilter(registry.filters.ModuleAdded);
for (const event of events) {
const plugin = await getPlugin(hre, event.args.module);
const metadata = await loadPluginMetadata(hre, plugin);
console.log(event.args.module, metadata);
}
});

export { }
export { };
16 changes: 8 additions & 8 deletions contracts/src/utils/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { HardhatRuntimeEnvironment } from "hardhat/types";
interface PluginMetadata {
name: string;
version: string;
requiresRootAccess: boolean;
permissions: number;
iconUrl: string;
appUrl: string;
}
Expand All @@ -16,25 +16,25 @@ interface PluginMetadata {
const ProviderType_Contract = 2n;
const ProviderType_Event = BigInt(3);

const MetadataEvent: string[] = ["event Metadata(bytes32 indexed metadataHash, bytes data)"]
const PluginMetadataType: string[] = ["string name", "string version", "bool requiresRootAccess", "string iconUrl", "string appUrl"];
const MetadataEvent: string[] = ["event Metadata(bytes32 indexed metadataHash, bytes data)"];
const PluginMetadataType: string[] = ["string name", "string version", "uint8 permissions", "string iconUrl", "string appUrl"];

const loadPluginMetadataFromContract = async (hre: HardhatRuntimeEnvironment, provider: string, metadataHash: string): Promise<string> => {
const providerInstance = await getInstance<IMetadataProvider>(hre, "IMetadataProvider", provider);
return await providerInstance.retrieveMetadata(metadataHash);
};

const loadPluginMetadataFromEvent = async (hre: HardhatRuntimeEnvironment, provider: string, metadataHash: string): Promise<string> => {
const eventInterface = new Interface(MetadataEvent)
const eventInterface = new Interface(MetadataEvent);
const events = await hre.ethers.provider.getLogs({
address: provider,
topics: eventInterface.encodeFilterTopics("Metadata", [metadataHash]),
fromBlock: "earliest",
toBlock: "latest"
})
toBlock: "latest",
});
if (events.length == 0) throw Error("Metadata not found");
const metadataEvent = events[events.length - 1];
const decodedEvent = eventInterface.decodeEventLog("Metadata", metadataEvent.data, metadataEvent.topics)
const decodedEvent = eventInterface.decodeEventLog("Metadata", metadataEvent.data, metadataEvent.topics);
return decodedEvent.data;
};

Expand Down Expand Up @@ -66,7 +66,7 @@ export const decodePluginMetadata = (data: string): PluginMetadata => {
return {
name: decoded[0],
version: decoded[1],
requiresRootAccess: decoded[2],
permissions: decoded[2],
iconUrl: decoded[3],
appUrl: decoded[4],
};
Expand Down
Loading

0 comments on commit 6d911b9

Please sign in to comment.