Skip to content

Commit

Permalink
refactor: move FunctionReference type to IPluginManager
Browse files Browse the repository at this point in the history
  • Loading branch information
jaypaik committed Jan 22, 2024
1 parent e90b425 commit 071e649
Show file tree
Hide file tree
Showing 28 changed files with 76 additions and 64 deletions.
2 changes: 1 addition & 1 deletion src/account/AccountLoupe.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ pragma solidity ^0.8.22;
import {KnownSelectors} from "../helpers/KnownSelectors.sol";

import {IAccountLoupe} from "../interfaces/IAccountLoupe.sol";
import {FunctionReference} from "../interfaces/IPluginManager.sol";

import {AccountStorageV1} from "../libraries/AccountStorageV1.sol";
import {CastLib} from "../libraries/CastLib.sol";
import {CountableLinkedListSetLib} from "../libraries/CountableLinkedListSetLib.sol";
import {FunctionReference} from "../libraries/FunctionReferenceLib.sol";
import {LinkedListSet, LinkedListSetLib} from "../libraries/LinkedListSetLib.sol";

/// @title Account Loupe
Expand Down
23 changes: 12 additions & 11 deletions src/account/PluginManagerInternals.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ import {
ManifestFunction,
PluginManifest
} from "../interfaces/IPlugin.sol";
import {IPluginManager} from "../interfaces/IPluginManager.sol";
import {FunctionReference, IPluginManager} from "../interfaces/IPluginManager.sol";

import {AccountStorageV1} from "../libraries/AccountStorageV1.sol";
import {CastLib} from "../libraries/CastLib.sol";
import {CountableLinkedListSetLib} from "../libraries/CountableLinkedListSetLib.sol";
import {FunctionReference, FunctionReferenceLib} from "../libraries/FunctionReferenceLib.sol";
import {FunctionReferenceLib} from "../libraries/FunctionReferenceLib.sol";
import {LinkedListSet, LinkedListSetLib} from "../libraries/LinkedListSetLib.sol";

/// @title Plugin Manager Internals
Expand All @@ -28,6 +28,7 @@ import {LinkedListSet, LinkedListSetLib} from "../libraries/LinkedListSetLib.sol
abstract contract PluginManagerInternals is IPluginManager, AccountStorageV1 {
using LinkedListSetLib for LinkedListSet;
using CountableLinkedListSetLib for LinkedListSet;
using FunctionReferenceLib for FunctionReference;

// Grouping of arguments to `uninstallPlugin` to avoid "stack too deep"
// errors when building without via-ir.
Expand Down Expand Up @@ -102,7 +103,7 @@ abstract contract PluginManagerInternals is IPluginManager, AccountStorageV1 {

SelectorData storage selectorData = _getAccountStorage().selectorData[selector];

if (selectorData.userOpValidation != FunctionReferenceLib._EMPTY_FUNCTION_REFERENCE) {
if (!selectorData.userOpValidation.isEmpty()) {
revert UserOpValidationFunctionAlreadySet(selector, validationFunction);
}

Expand All @@ -114,7 +115,7 @@ abstract contract PluginManagerInternals is IPluginManager, AccountStorageV1 {

SelectorData storage selectorData = _getAccountStorage().selectorData[selector];

if (selectorData.runtimeValidation != FunctionReferenceLib._EMPTY_FUNCTION_REFERENCE) {
if (!selectorData.runtimeValidation.isEmpty()) {
revert RuntimeValidationFunctionAlreadySet(selector, validationFunction);
}

Expand All @@ -128,9 +129,9 @@ abstract contract PluginManagerInternals is IPluginManager, AccountStorageV1 {

_addHooks(selectorData.executionHooks, selector, preExecHook, postExecHook);

if (preExecHook != FunctionReferenceLib._EMPTY_FUNCTION_REFERENCE) {
if (!preExecHook.isEmpty()) {
selectorData.hasPreExecHooks = true;
} else if (postExecHook != FunctionReferenceLib._EMPTY_FUNCTION_REFERENCE) {
} else if (!postExecHook.isEmpty()) {
// Only set this flag if the pre hook is empty and the post hook is non-empty.
selectorData.hasPostOnlyExecHooks = true;
}
Expand Down Expand Up @@ -159,13 +160,13 @@ abstract contract PluginManagerInternals is IPluginManager, AccountStorageV1 {
FunctionReference preExecHook,
FunctionReference postExecHook
) internal {
if (preExecHook != FunctionReferenceLib._EMPTY_FUNCTION_REFERENCE) {
if (!preExecHook.isEmpty()) {
// add pre or pre/post pair of exec hooks
if (!hooks.preHooks.tryIncrement(CastLib.toSetValue(preExecHook))) {
revert DuplicateHookLimitExceeded(selector, preExecHook);
}

if (postExecHook != FunctionReferenceLib._EMPTY_FUNCTION_REFERENCE) {
if (!postExecHook.isEmpty()) {
// can ignore return val of tryEnableFlags here as tryIncrement above must have succeeded
hooks.preHooks.tryEnableFlags(CastLib.toSetValue(preExecHook), _PRE_EXEC_HOOK_HAS_POST_FLAG);
if (!hooks.associatedPostHooks[preExecHook].tryIncrement(CastLib.toSetValue(postExecHook))) {
Expand All @@ -186,7 +187,7 @@ abstract contract PluginManagerInternals is IPluginManager, AccountStorageV1 {
internal
returns (bool shouldClearHasPreHooks, bool shouldClearHasPostOnlyHooks)
{
if (preExecHook != FunctionReferenceLib._EMPTY_FUNCTION_REFERENCE) {
if (!preExecHook.isEmpty()) {
// If decrementing results in removal, this also clears the flag _PRE_EXEC_HOOK_HAS_POST_FLAG.
// Can ignore the return value because the manifest was checked to match the hash.
hooks.preHooks.tryDecrement(CastLib.toSetValue(preExecHook));
Expand All @@ -197,7 +198,7 @@ abstract contract PluginManagerInternals is IPluginManager, AccountStorageV1 {
shouldClearHasPreHooks = true;
}

if (postExecHook != FunctionReferenceLib._EMPTY_FUNCTION_REFERENCE) {
if (!postExecHook.isEmpty()) {
// Remove the associated post-exec hook, if it is set to the expected value.
// Can ignore the return value because the manifest was checked to match the hash.
hooks.associatedPostHooks[preExecHook].tryDecrement(CastLib.toSetValue(postExecHook));
Expand Down Expand Up @@ -655,7 +656,7 @@ abstract contract PluginManagerInternals is IPluginManager, AccountStorageV1 {
}

function _assertNotNullFunction(FunctionReference functionReference) internal pure {
if (functionReference == FunctionReferenceLib._EMPTY_FUNCTION_REFERENCE) {
if (functionReference.isEmpty()) {
revert NullFunctionReference();
}
}
Expand Down
9 changes: 5 additions & 4 deletions src/account/UpgradeableModularAccount.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ import {IAccountView} from "../interfaces/IAccountView.sol";
import {IEntryPoint} from "../interfaces/erc4337/IEntryPoint.sol";
import {IPlugin, PluginManifest} from "../interfaces/IPlugin.sol";
import {IPluginExecutor} from "../interfaces/IPluginExecutor.sol";
import {IPluginManager} from "../interfaces/IPluginManager.sol";
import {FunctionReference, IPluginManager} from "../interfaces/IPluginManager.sol";
import {UserOperation} from "../interfaces/erc4337/UserOperation.sol";

import {CastLib} from "../libraries/CastLib.sol";
import {CountableLinkedListSetLib} from "../libraries/CountableLinkedListSetLib.sol";
import {FunctionReference, FunctionReferenceLib} from "../libraries/FunctionReferenceLib.sol";
import {FunctionReferenceLib} from "../libraries/FunctionReferenceLib.sol";
import {LinkedListSet, LinkedListSetLib} from "../libraries/LinkedListSetLib.sol";
import {UUPSUpgradeable} from "../../ext/UUPSUpgradeable.sol";

Expand All @@ -44,6 +44,7 @@ contract UpgradeableModularAccount is
{
using CountableLinkedListSetLib for LinkedListSet;
using LinkedListSetLib for LinkedListSet;
using FunctionReferenceLib for FunctionReference;

/// @dev Struct to hold optional configuration data for uninstalling a plugin. This should be encoded and
/// passed to the `config` parameter of `uninstallPlugin`.
Expand Down Expand Up @@ -419,7 +420,7 @@ contract UpgradeableModularAccount is
bytes32 userOpHash,
bool doPreValidationHooks
) internal returns (uint256 validationData) {
if (userOpValidationFunction == FunctionReferenceLib._EMPTY_FUNCTION_REFERENCE) {
if (userOpValidationFunction.isEmpty()) {
revert UserOpValidationFunctionMissing(selector);
}

Expand Down Expand Up @@ -518,7 +519,7 @@ contract UpgradeableModularAccount is
{
if (runtimeValidationFunction.isEmptyOrMagicValue()) {
if (
runtimeValidationFunction == FunctionReferenceLib._EMPTY_FUNCTION_REFERENCE
runtimeValidationFunction.isEmpty()
&& (
(
msg.sig != IPluginManager.installPlugin.selector
Expand Down
2 changes: 1 addition & 1 deletion src/interfaces/IAccountLoupe.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.22;

import {FunctionReference} from "../libraries/FunctionReferenceLib.sol";
import {FunctionReference} from "./IPluginManager.sol";

/// @title Account Loupe Interface
interface IAccountLoupe {
Expand Down
2 changes: 1 addition & 1 deletion src/interfaces/IPluginManager.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.22;

import {FunctionReference} from "../libraries/FunctionReferenceLib.sol";
type FunctionReference is bytes21;

/// @title Plugin Manager Interface
interface IPluginManager {
Expand Down
2 changes: 1 addition & 1 deletion src/libraries/AccountStorageV1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
pragma solidity ^0.8.22;

import {IPlugin} from "../interfaces/IPlugin.sol";
import {FunctionReference} from "../interfaces/IPluginManager.sol";

import {FunctionReference} from "../libraries/FunctionReferenceLib.sol";
import {LinkedListSet} from "../libraries/LinkedListSetLib.sol";

/// @title Account Storage V1
Expand Down
21 changes: 11 additions & 10 deletions src/libraries/FunctionReferenceLib.sol
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.22;

type FunctionReference is bytes21;

using {eq as ==, notEq as !=} for FunctionReference global;
using FunctionReferenceLib for FunctionReference global;
import {FunctionReference} from "../interfaces/IPluginManager.sol";

/// @title Function Reference Lib
/// @author Alchemy
Expand All @@ -30,12 +27,16 @@ library FunctionReferenceLib {
function isEmptyOrMagicValue(FunctionReference fr) internal pure returns (bool) {
return FunctionReference.unwrap(fr) <= bytes21(uint168(2));
}
}

function eq(FunctionReference a, FunctionReference b) pure returns (bool) {
return FunctionReference.unwrap(a) == FunctionReference.unwrap(b);
}
function isEmpty(FunctionReference fr) internal pure returns (bool) {
return FunctionReference.unwrap(fr) == bytes21(0);
}

function notEq(FunctionReference a, FunctionReference b) pure returns (bool) {
return FunctionReference.unwrap(a) != FunctionReference.unwrap(b);
function eq(FunctionReference a, FunctionReference b) internal pure returns (bool) {
return FunctionReference.unwrap(a) == FunctionReference.unwrap(b);
}

function notEq(FunctionReference a, FunctionReference b) internal pure returns (bool) {
return FunctionReference.unwrap(a) != FunctionReference.unwrap(b);
}
}
3 changes: 2 additions & 1 deletion test/account/AccountExecHooks.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.so
import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol";
import {MultiOwnerPlugin} from "../../src/plugins/owner/MultiOwnerPlugin.sol";
import {IEntryPoint} from "../../src/interfaces/erc4337/IEntryPoint.sol";
import {FunctionReference, FunctionReferenceLib} from "../../src/libraries/FunctionReferenceLib.sol";
import {FunctionReference} from "../../src/interfaces/IPluginManager.sol";
import {FunctionReferenceLib} from "../../src/libraries/FunctionReferenceLib.sol";
import {
IPlugin,
ManifestExecutionHook,
Expand Down
4 changes: 2 additions & 2 deletions test/account/AccountLoupe.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ import {
PluginManifest
} from "../../src/interfaces/IPlugin.sol";
import {IAccountLoupe} from "../../src/interfaces/IAccountLoupe.sol";
import {IPluginManager} from "../../src/interfaces/IPluginManager.sol";
import {FunctionReference, IPluginManager} from "../../src/interfaces/IPluginManager.sol";
import {IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol";
import {FunctionReference, FunctionReferenceLib} from "../../src/libraries/FunctionReferenceLib.sol";
import {FunctionReferenceLib} from "../../src/libraries/FunctionReferenceLib.sol";

import {MultiOwnerMSCAFactory} from "../../src/factory/MultiOwnerMSCAFactory.sol";
import {ComprehensivePlugin} from "../mocks/plugins/ComprehensivePlugin.sol";
Expand Down
3 changes: 2 additions & 1 deletion test/account/AccountPreValidationHooks.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import {IMultiOwnerPlugin} from "../../src/plugins/owner/IMultiOwnerPlugin.sol";
import {MultiOwnerPlugin} from "../../src/plugins/owner/MultiOwnerPlugin.sol";
import {IEntryPoint} from "../../src/interfaces/erc4337/IEntryPoint.sol";
import {UserOperation} from "../../src/interfaces/erc4337/UserOperation.sol";
import {FunctionReference, FunctionReferenceLib} from "../../src/libraries/FunctionReferenceLib.sol";
import {FunctionReference} from "../../src/interfaces/IPluginManager.sol";
import {FunctionReferenceLib} from "../../src/libraries/FunctionReferenceLib.sol";
import {
IPlugin,
PluginManifest,
Expand Down
2 changes: 1 addition & 1 deletion test/account/AccountReturnData.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.so

import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol";
import {IEntryPoint} from "../../src/interfaces/erc4337/IEntryPoint.sol";
import {FunctionReference} from "../../src/interfaces/IPluginManager.sol";
import {MultiOwnerPlugin} from "../../src/plugins/owner/MultiOwnerPlugin.sol";
import {FunctionReference} from "../../src/libraries/FunctionReferenceLib.sol";
import {Call} from "../../src/interfaces/IStandardExecutor.sol";

import {
Expand Down
2 changes: 1 addition & 1 deletion test/account/ExecuteFromPluginPermissions.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.so
import {IPlugin} from "../../src/interfaces/IPlugin.sol";
import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol";
import {IEntryPoint} from "../../src/interfaces/erc4337/IEntryPoint.sol";
import {FunctionReference} from "../../src/interfaces/IPluginManager.sol";
import {MultiOwnerPlugin} from "../../src/plugins/owner/MultiOwnerPlugin.sol";
import {FunctionReference} from "../../src/libraries/FunctionReferenceLib.sol";

import {MultiOwnerMSCAFactory} from "../../src/factory/MultiOwnerMSCAFactory.sol";

Expand Down
2 changes: 1 addition & 1 deletion test/account/ManifestValidity.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.so
import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol";
import {PluginManagerInternals} from "../../src/account/PluginManagerInternals.sol";
import {IEntryPoint} from "../../src/interfaces/erc4337/IEntryPoint.sol";
import {FunctionReference} from "../../src/interfaces/IPluginManager.sol";
import {MultiOwnerPlugin} from "../../src/plugins/owner/MultiOwnerPlugin.sol";
import {FunctionReference} from "../../src/libraries/FunctionReferenceLib.sol";

import {MultiOwnerMSCAFactory} from "../../src/factory/MultiOwnerMSCAFactory.sol";
import {
Expand Down
3 changes: 1 addition & 2 deletions test/account/UpgradeableModularAccount.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,8 @@ import {IEntryPoint} from "../../src/interfaces/erc4337/IEntryPoint.sol";
import {UserOperation} from "../../src/interfaces/erc4337/UserOperation.sol";
import {IAccountInitializable} from "../../src/interfaces/IAccountInitializable.sol";
import {IPlugin, PluginManifest} from "../../src/interfaces/IPlugin.sol";
import {IPluginManager} from "../../src/interfaces/IPluginManager.sol";
import {FunctionReference, IPluginManager} from "../../src/interfaces/IPluginManager.sol";
import {Call} from "../../src/interfaces/IStandardExecutor.sol";
import {FunctionReference} from "../../src/libraries/FunctionReferenceLib.sol";

import {Counter} from "../mocks/Counter.sol";
import {MultiOwnerMSCAFactory} from "../../src/factory/MultiOwnerMSCAFactory.sol";
Expand Down
4 changes: 2 additions & 2 deletions test/account/UpgradeableModularAccountPluginManager.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ import {TokenReceiverPlugin} from "../../src/plugins/TokenReceiverPlugin.sol";
import {IEntryPoint} from "../../src/interfaces/erc4337/IEntryPoint.sol";
import {PluginManifest} from "../../src/interfaces/IPlugin.sol";
import {IAccountLoupe} from "../../src/interfaces/IAccountLoupe.sol";
import {IPluginManager} from "../../src/interfaces/IPluginManager.sol";
import {FunctionReference, IPluginManager} from "../../src/interfaces/IPluginManager.sol";
import {IStandardExecutor} from "../../src/interfaces/IStandardExecutor.sol";
import {Call} from "../../src/interfaces/IStandardExecutor.sol";
import {FunctionReference, FunctionReferenceLib} from "../../src/libraries/FunctionReferenceLib.sol";
import {FunctionReferenceLib} from "../../src/libraries/FunctionReferenceLib.sol";
import {IPlugin, PluginManifest} from "../../src/interfaces/IPlugin.sol";

import {Counter} from "../mocks/Counter.sol";
Expand Down
2 changes: 1 addition & 1 deletion test/account/ValidationIntersection.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.so
import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol";
import {IEntryPoint} from "../../src/interfaces/erc4337/IEntryPoint.sol";
import {UserOperation} from "../../src/interfaces/erc4337/UserOperation.sol";
import {FunctionReference} from "../../src/interfaces/IPluginManager.sol";
import {MultiOwnerPlugin} from "../../src/plugins/owner/MultiOwnerPlugin.sol";
import {FunctionReference} from "../../src/libraries/FunctionReferenceLib.sol";

import {MultiOwnerMSCAFactory} from "../../src/factory/MultiOwnerMSCAFactory.sol";
import {
Expand Down
4 changes: 2 additions & 2 deletions test/account/phases/AccountStatePhases.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {UpgradeableModularAccount} from "../../../src/account/UpgradeableModular
import {MultiOwnerPlugin} from "../../../src/plugins/owner/MultiOwnerPlugin.sol";
import {IEntryPoint} from "../../../src/interfaces/erc4337/IEntryPoint.sol";
import {UserOperation} from "../../../src/interfaces/erc4337/UserOperation.sol";
import {IPluginManager} from "../../../src/interfaces/IPluginManager.sol";
import {FunctionReference, IPluginManager} from "../../../src/interfaces/IPluginManager.sol";
import {IStandardExecutor, Call} from "../../../src/interfaces/IStandardExecutor.sol";
import {
IPlugin,
Expand All @@ -20,7 +20,7 @@ import {
ManifestAssociatedFunctionType,
ManifestAssociatedFunction
} from "../../../src/interfaces/IPlugin.sol";
import {FunctionReference, FunctionReferenceLib} from "../../../src/libraries/FunctionReferenceLib.sol";
import {FunctionReferenceLib} from "../../../src/libraries/FunctionReferenceLib.sol";
import {MultiOwnerMSCAFactory} from "../../../src/factory/MultiOwnerMSCAFactory.sol";

import {AccountStateMutatingPlugin} from "../../mocks/plugins/AccountStateMutatingPlugin.sol";
Expand Down
25 changes: 14 additions & 11 deletions test/libraries/FunctionReferenceLib.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
pragma solidity ^0.8.22;

import {Test} from "forge-std/Test.sol";
import {FunctionReference, FunctionReferenceLib} from "../../src/libraries/FunctionReferenceLib.sol";
import {FunctionReference} from "../../src/interfaces/IPluginManager.sol";
import {FunctionReferenceLib} from "../../src/libraries/FunctionReferenceLib.sol";

contract FunctionReferenceLibTest is Test {
using FunctionReferenceLib for FunctionReference;

function testFuzz_functionReference_packing(address addr, uint8 functionId) public {
// console.log("addr: ", addr);
// console.log("functionId: ", vm.toString(functionId));
Expand All @@ -18,19 +21,19 @@ contract FunctionReferenceLibTest is Test {
}

function testFuzz_functionReference_operators(FunctionReference a, FunctionReference b) public {
assertTrue(a == a);
assertTrue(b == b);
assertTrue(a.eq(a));
assertTrue(b.eq(b));

if (FunctionReference.unwrap(a) == FunctionReference.unwrap(b)) {
assertTrue(a == b);
assertTrue(b == a);
assertFalse(a != b);
assertFalse(b != a);
assertTrue(a.eq(b));
assertTrue(b.eq(a));
assertFalse(a.notEq(b));
assertFalse(b.notEq(a));
} else {
assertTrue(a != b);
assertTrue(b != a);
assertFalse(a == b);
assertFalse(b == a);
assertTrue(a.notEq(b));
assertTrue(b.notEq(a));
assertFalse(a.eq(b));
assertFalse(b.eq(a));
}
}
}
2 changes: 1 addition & 1 deletion test/mocks/plugins/ExecFromPluginPermissionsMocks.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import {
import {IStandardExecutor} from "../../../src/interfaces/IStandardExecutor.sol";
import {IPluginExecutor} from "../../../src/interfaces/IPluginExecutor.sol";
import {IPlugin} from "../../../src/interfaces/IPlugin.sol";
import {FunctionReference} from "../../../src/interfaces/IPluginManager.sol";
import {BaseTestPlugin} from "./BaseTestPlugin.sol";
import {FunctionReference} from "../../../src/libraries/FunctionReferenceLib.sol";

import {ResultCreatorPlugin} from "./ReturnDataPluginMocks.sol";
import {Counter} from "../Counter.sol";
Expand Down
Loading

0 comments on commit 071e649

Please sign in to comment.