This repository has been archived by the owner on Oct 27, 2023. It is now read-only.
forked from 5afe/safe-core-protocol-demo
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add logic to set and retrieve plugin meta data
- Loading branch information
Showing
8 changed files
with
182 additions
and
45 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
// SPDX-License-Identifier: LGPL-3.0-only | ||
pragma solidity ^0.8.18; | ||
|
||
import {ISafe} from "./interfaces/Accounts.sol"; | ||
import {ISafeProtocolPlugin} from "./interfaces/Integrations.sol"; | ||
import {ISafeProtocolManager} from "./interfaces/Manager.sol"; | ||
import {SafeTransaction, SafeRootAccess} from "./DataTypes.sol"; | ||
|
||
enum MetaDataProviderType { | ||
IPFS, | ||
URL, | ||
Contract, | ||
Event | ||
} | ||
|
||
interface MetaDataProvider { | ||
function retrieveMetaData(bytes32 metaDataHash) external view returns (bytes memory metaData); | ||
} | ||
|
||
struct PluginMetaData { | ||
string name; | ||
string version; | ||
bool requiresRootAccess; | ||
string iconUrl; | ||
string appUrl; | ||
} | ||
|
||
library PluginMetaDataOps { | ||
function encode(PluginMetaData memory data) internal pure returns (bytes memory) { | ||
return | ||
abi.encodePacked( | ||
uint8(0x00), // Format | ||
uint8(0x00), // Format version | ||
abi.encode(data.name, data.version, data.requiresRootAccess, data.iconUrl, data.appUrl) // Meta Data | ||
); | ||
} | ||
|
||
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( | ||
data[2:], | ||
(string, string, bool, string, string) | ||
); | ||
return PluginMetaData(name, version, requiresRootAccess, iconUrl, appUrl); | ||
} | ||
} | ||
|
||
abstract contract BasePlugin is ISafeProtocolPlugin, MetaDataProvider { | ||
using PluginMetaDataOps for PluginMetaData; | ||
|
||
string public name; | ||
string public version; | ||
bool public immutable requiresRootAccess; | ||
bytes32 public immutable metaDataHash; | ||
bytes private encodedMetaData; | ||
|
||
constructor(PluginMetaData memory metaData) { | ||
name = metaData.name; | ||
version = metaData.version; | ||
requiresRootAccess = metaData.requiresRootAccess; | ||
// MetaData Format + Format Version + Encoded MetaData | ||
encodedMetaData = metaData.encode(); | ||
metaDataHash = keccak256(encodedMetaData); | ||
} | ||
|
||
function metaProvider() external view override returns (uint256 providerType, bytes memory location) { | ||
providerType = uint256(MetaDataProviderType.Contract); | ||
location = abi.encode(address(this)); | ||
} | ||
|
||
function retrieveMetaData(bytes32 _metaDataHash) external view returns (bytes memory metaData) { | ||
require(metaDataHash == _metaDataHash, "Cannot retrieve meta data"); | ||
return encodedMetaData; | ||
} | ||
} | ||
|
||
contract SamplePlugin is BasePlugin { | ||
constructor() | ||
BasePlugin(PluginMetaData({name: "Sample Plugin", version: "1.0.0", requiresRootAccess: false, iconUrl: "", appUrl: ""})) | ||
{} | ||
|
||
function executeFromPlugin( | ||
ISafeProtocolManager manager, | ||
ISafe safe, | ||
SafeTransaction calldata safetx | ||
) external returns (bytes[] memory data) { | ||
(data) = manager.executeTransaction(safe, safetx); | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,15 @@ | ||
import { BaseContract } from "ethers"; | ||
import { Addressable, BaseContract } from "ethers"; | ||
import hre, { deployments } from "hardhat"; | ||
import { SamplePlugin } from "../../typechain-types"; | ||
|
||
export const getInstance = async<T extends BaseContract>(name: string): Promise<T> => { | ||
export const getInstance = async<T extends BaseContract>(name: string, address: string): Promise<T> => { | ||
// TODO: this typecasting should be refactored | ||
return (await hre.ethers.getContractAt(name, address) as unknown) as T; | ||
}; | ||
|
||
export const getSingleton = async<T extends BaseContract>(name: string): Promise<T> => { | ||
const deployment = await deployments.get(name); | ||
const Contract = await hre.ethers.getContractFactory(name); | ||
return Contract.attach(deployment.address) as T; | ||
return getInstance<T>(name, deployment.address) | ||
}; | ||
|
||
export const getSamplePlugin = () => getInstance<SamplePlugin>("SamplePlugin") | ||
export const getSamplePlugin = () => getSingleton<SamplePlugin>("SamplePlugin") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import { AbiCoder, ParamType, isHexString, keccak256 } from "ethers" | ||
import { BasePlugin, MetaDataProvider, MetaDataProvider__factory } from "../../typechain-types" | ||
import { getInstance } from "./contracts"; | ||
|
||
interface PluginMetaData { | ||
name: string, | ||
version: string, | ||
requiresRootAccess: boolean, | ||
iconUrl: string, | ||
appUrl: string | ||
} | ||
|
||
const ProviderType_IPFS = 0n; | ||
const ProviderType_URL = 1n; | ||
const ProviderType_Contract = 2n; | ||
const ProviderType_Event = 3n; | ||
|
||
const PluginMetaDataType: string[] = [ | ||
"string name", | ||
"string version", | ||
"bool requiresRootAccess", | ||
"string iconUrl", | ||
"string appUrl" | ||
] | ||
|
||
const loadPluginMetaDataFromContract = async (provider: string, metaDataHash: string): Promise<string> => { | ||
const providerInstance = await getInstance<MetaDataProvider>("MetaDataProvider", provider) | ||
return await providerInstance.retrieveMetaData(metaDataHash) | ||
} | ||
|
||
const loadRawMetaData = async(plugin: BasePlugin, metaDataHash: string): Promise<string> => { | ||
const [type, source] = await plugin.metaProvider() | ||
switch(type) { | ||
case ProviderType_Contract: | ||
return loadPluginMetaDataFromContract(AbiCoder.defaultAbiCoder().decode(["address"], source)[0], metaDataHash) | ||
default: | ||
throw Error("Unsupported MetaDataProviderType") | ||
} | ||
} | ||
|
||
export const loadPluginMetaData = async (plugin: BasePlugin): Promise<PluginMetaData> => { | ||
const metaDataHash = await plugin.metaDataHash() | ||
const metaData = await loadRawMetaData(plugin, metaDataHash) | ||
if (metaDataHash !== keccak256(metaData)) throw Error("Invalid meta data retrieved!") | ||
return decodePluginMetaData(metaData) | ||
} | ||
|
||
export const decodePluginMetaData = (data: string): PluginMetaData => { | ||
if (!isHexString(data)) throw Error("Invalid data format"); | ||
const format = data.slice(2, 6) | ||
if (format !== "0000") throw Error("Unsupported format or format version"); | ||
const metaData = data.slice(6) | ||
const decoded = AbiCoder.defaultAbiCoder().decode( | ||
PluginMetaDataType, | ||
"0x" + metaData | ||
) | ||
return { | ||
name: decoded[0], | ||
version: decoded[1], | ||
requiresRootAccess: decoded[2], | ||
iconUrl: decoded[3], | ||
appUrl: decoded[4] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters