Replaced delegatecall with custom executor (#6)

* Removed delegatecall support in favor of a permissioned executor

* Cleanup

* Fixed issues with Vault interface
This commit is contained in:
Jayden Windle
2023-01-08 22:16:18 -08:00
committed by GitHub
parent c1d90a1864
commit dcc9eb77ed
9 changed files with 160 additions and 404 deletions

12
src/MinimalReceiver.sol Normal file
View File

@@ -0,0 +1,12 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "openzeppelin-contracts/token/ERC721/utils/ERC721Holder.sol";
import "openzeppelin-contracts/token/ERC1155/utils/ERC1155Holder.sol";
contract MinimalReceiver is ERC721Holder, ERC1155Holder {
/**
* @dev Allows all Ether transfers
*/
receive() external payable virtual {}
}

View File

@@ -1,28 +1,63 @@
// SPDX-License-Identifier: UNLICENSED // SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13; pragma solidity ^0.8.13;
import "forge-std/Script.sol";
import "openzeppelin-contracts/token/ERC721/IERC721.sol";
import "openzeppelin-contracts/token/ERC721/IERC721Receiver.sol"; import "openzeppelin-contracts/token/ERC721/IERC721Receiver.sol";
import "openzeppelin-contracts/token/ERC1155/IERC1155Receiver.sol"; import "openzeppelin-contracts/token/ERC1155/IERC1155Receiver.sol";
import "openzeppelin-contracts/interfaces/IERC1271.sol"; import "openzeppelin-contracts/interfaces/IERC1271.sol";
import "openzeppelin-contracts/utils/cryptography/SignatureChecker.sol"; import "openzeppelin-contracts/utils/cryptography/SignatureChecker.sol";
import "openzeppelin-contracts/utils/Address.sol";
import "./VaultRegistry.sol"; import "./MinimalReceiver.sol";
import "./interfaces/IVault.sol"; import "./interfaces/IVault.sol";
import "./interfaces/IExecutionModule.sol";
import "./lib/MinimalProxyStore.sol"; import "./lib/MinimalProxyStore.sol";
import "./lib/Delegate.sol";
/** /**
* @title A smart contract wallet owned by a single ERC721 token * @title A smart contract wallet owned by a single ERC721 token
* @author Jayden Windle (jaydenwindle) * @author Jayden Windle (jaydenwindle)
*/ */
contract Vault is IVault { contract Vault is IVault, MinimalReceiver {
error NotAuthorized(); error NotAuthorized();
error VaultLocked();
/** /**
* @dev Address of VaultRegistry * @dev Timestamp at which Vault will unlock
*/ */
VaultRegistry public immutable registry = VaultRegistry(msg.sender); uint256 public unlockTimestamp;
/**
* @dev Mapping from owner address to executor address
*/
mapping(address => address) public executor;
/**
* @dev If vault is unlocked and an executor is set, pass call to executor
*/
fallback(bytes calldata data)
external
payable
returns (bytes memory result)
{
if (unlockTimestamp > block.timestamp) revert VaultLocked();
address _owner = owner();
address _executor = executor[_owner];
// accept funds if executor is undefined or cannot be called
if (_executor == address(0)) return "";
if (_executor.code.length == 0) return "";
bool success;
(success, result) = _executor.call(data);
if (!success) {
assembly {
revert(add(result, 32), mload(result))
}
}
}
/** /**
* @dev Executes a transaction from the Vault. Must be called by an authorized sender. * @dev Executes a transaction from the Vault. Must be called by an authorized sender.
@@ -34,13 +69,13 @@ contract Vault is IVault {
function executeCall( function executeCall(
address payable to, address payable to,
uint256 value, uint256 value,
bytes calldata data, bytes calldata data
bool useExecutionModule ) external payable returns (bytes memory result) {
) external payable { if (unlockTimestamp > block.timestamp) revert VaultLocked();
if (!_isAuthorized(msg.sender, useExecutionModule)) if (!isOwnerOrExecutor(msg.sender)) revert NotAuthorized();
revert NotAuthorized();
(bool success, bytes memory result) = to.call{value: value}(data); bool success;
(success, result) = to.call{value: value}(data);
if (!success) { if (!success) {
assembly { assembly {
@@ -50,26 +85,51 @@ contract Vault is IVault {
} }
/** /**
* @dev Executes a delegated transaction from the Vault, allowing vault * @dev Sets executior address for Vault, allowing owner to use a custom implementation if they choose to.
* functionality to be expanded without setting an execution module. Must be called by an authorized sender. * When the token controlling the vault is transferred, the implementation address will reset
* *
* @param to Contract address of the delegated call * @param _executionModule the address of the execution module
* @param data Encoded payload of the delegated call
*/ */
function executeDelegateCall( function setExecutor(address _executionModule) external {
address payable to, if (unlockTimestamp > block.timestamp) revert VaultLocked();
bytes calldata data,
bool useExecutionModule
) external payable {
if (!_isAuthorized(msg.sender, useExecutionModule))
revert NotAuthorized();
(bool success, bytes memory result) = to.delegatecall(data); address _owner = owner();
if (!success) { if (_owner != msg.sender) revert NotAuthorized();
assembly {
revert(add(result, 32), mload(result)) executor[_owner] = _executionModule;
} }
}
/**
* @dev Locks Vault, preventing transactions from being executed until a certain time
*
* @param _unlockTimestamp timestamp when the vault will become unlocked
*/
function lock(uint256 _unlockTimestamp) external {
if (unlockTimestamp > block.timestamp) revert VaultLocked();
address _owner = owner();
if (_owner != msg.sender) revert NotAuthorized();
unlockTimestamp = _unlockTimestamp;
}
/**
* @dev Returns Vault lock status
*
* @return true if Vault is locked, false otherwise
*/
function isLocked() external view returns (bool) {
return unlockTimestamp > block.timestamp;
}
/**
* @dev Returns true if caller is authorized to execute actions on this vault
*
* @param caller the address to query authorization for
* @return true if caller is authorized, false otherwise
*/
function isAuthorized(address caller) external view returns (bool) {
return isOwnerOrExecutor(caller);
} }
/** /**
@@ -83,33 +143,22 @@ contract Vault is IVault {
view view
returns (bytes4 magicValue) returns (bytes4 magicValue)
{ {
// If vault is locked, return invalid for all signatures // If vault is locked, disable signing
bool isLocked = registry.vaultLocked(address(this)); if (unlockTimestamp > block.timestamp) return "";
if (isLocked) {
return "";
}
// If vault has an executionModule, return its verification result // If vault has an executor, check if executor signature is valid
address _owner = owner(); address _owner = owner();
address executionModule = registry.vaultExecutionModule( address _executor = executor[_owner];
address(this),
_owner if (
); _executor != address(0) &&
if (executionModule != address(0)) { SignatureChecker.isValidSignatureNow(_executor, hash, signature)
return ) {
IExecutionModule(executionModule).isValidSignature( return IERC1271.isValidSignature.selector;
hash,
signature
);
} }
// Default - check if signature is valid for vault owner // Default - check if signature is valid for vault owner
bool isValid = SignatureChecker.isValidSignatureNow( if (SignatureChecker.isValidSignatureNow(_owner, hash, signature)) {
_owner,
hash,
signature
);
if (isValid) {
return IERC1271.isValidSignature.selector; return IERC1271.isValidSignature.selector;
} }
@@ -117,7 +166,7 @@ contract Vault is IVault {
} }
/** /**
* @dev Returns the owner of the token that controls this Vault (for Ownable compatibility) * @dev Returns the owner of the token that controls this Vault (public for Ownable compatibility)
* *
* @return the address of the Vault owner * @return the address of the Vault owner
*/ */
@@ -135,105 +184,18 @@ contract Vault is IVault {
} }
/** /**
* @dev Returns true if caller is authorized to execute actions on this vault. Only uses execution module for auth * @dev Returns true if caller is owner or ececutor
* if useExecutionModule is set to true.
* *
* @param caller the address to query authorization for * @param caller the address to query for
* @return bool true if caller is authorized, false otherwise * @return true if caller is owner or executor, false otherwise
*/ */
function _isAuthorized(address caller, bool useExecutionModule) function isOwnerOrExecutor(address caller) internal view returns (bool) {
internal
view
returns (bool)
{
// If vault is locked, return false for all auth queries
bool isLocked = registry.vaultLocked(address(this));
if (isLocked) {
return false;
}
address _owner = owner(); address _owner = owner();
if (caller == _owner) return true;
// If useExecutionModule is set, lookup executionModule address _executor = executor[_owner];
address executionModule; if (caller == _executor) return true;
if (useExecutionModule) {
executionModule = registry.vaultExecutionModule(
address(this),
_owner
);
}
// if useExecutionModule is false or executionModule is not set, return default auth return false;
if (executionModule == address(0)) return caller == _owner;
// If executionModule is set, query it for auth status
return IExecutionModule(executionModule).isAuthorized(caller);
}
/**
* @dev Returns true if caller is authorized to execute actions on this vault
*
* @param caller the address to query authorization for
* @return bool true if caller is authorized, false otherwise
*/
function isAuthorized(address caller) public view virtual returns (bool) {
return _isAuthorized(caller, true);
}
/**
* @dev If vault is unlocked and an execution module is defined, delegate execution to the execution module
*/
fallback() external payable virtual {
address _owner = owner();
address executionModule = registry.vaultExecutionModule(
address(this),
_owner
);
bool isLocked = registry.vaultLocked(address(this));
if (!isLocked) Delegate.delegate(executionModule);
}
/**
* @dev Allows all Ether transfers
*/
receive() external payable virtual {}
/**
* @dev Allows all ERC721 tokens to be received
*/
function onERC721Received(
address,
address,
uint256,
bytes calldata
) external pure virtual returns (bytes4) {
return IERC721Receiver.onERC721Received.selector;
}
/**
* @dev Allows all ERC1155 tokens to be received
*/
function onERC1155Received(
address,
address,
uint256,
uint256,
bytes calldata /* data */
) external pure virtual returns (bytes4) {
return IERC1155Receiver.onERC1155Received.selector;
}
/**
* @dev Allows all ERC1155 token batches to be received
*/
function onERC1155BatchReceived(
address,
address,
uint256[] calldata,
uint256[] calldata,
bytes calldata
) external pure virtual returns (bytes4) {
return IERC1155Receiver.onERC1155BatchReceived.selector;
} }
} }

View File

@@ -25,16 +25,6 @@ contract VaultRegistry {
*/ */
address public vaultImplementation; address public vaultImplementation;
/**
* @dev Mapping from vault address to owner address to execution module address
*/
mapping(address => mapping(address => address)) private executionModule;
/**
* @dev Mapping from vault address unlock timestamp
*/
mapping(address => uint256) public unlockTimestamp;
/** /**
* @dev Deploys the default Vault implementation * @dev Deploys the default Vault implementation
*/ */
@@ -64,45 +54,6 @@ contract VaultRegistry {
return payable(vaultProxy); return payable(vaultProxy);
} }
/**
* @dev Sets the execution module address for a Vault, allowing for vault owners to use a custom implementation if
* they choose to. When the token controlling the vault is transferred, the implementation address will reset
*
* @param vault the address of the vault whose execution module is being set
* @param _executionModule the address of the execution module
*/
function setExecutionModule(address vault, address _executionModule)
external
{
if (vaultLocked(vault)) revert VaultLocked();
if (vault.code.length == 0) revert NotAuthorized();
address owner = vaultOwner(vault);
if (owner != msg.sender) revert NotAuthorized();
executionModule[vault][owner] = _executionModule;
}
/**
* @dev Locks a vault, preventing transactions from being executed until a certain time
*
* @param vault the vault to lock
* @param _unlockTimestamp timestamp when the vault will become unlocked
*/
function lockVault(address payable vault, uint256 _unlockTimestamp)
external
{
if (vaultLocked(vault)) revert VaultLocked();
if (vault.code.length == 0) revert NotAuthorized();
address owner = vaultOwner(vault);
if (owner != msg.sender) revert NotAuthorized();
unlockTimestamp[vault] = _unlockTimestamp;
}
/** /**
* @dev Gets the address of the VaultProxy for an ERC721 token. If VaultProxy is * @dev Gets the address of the VaultProxy for an ERC721 token. If VaultProxy is
* not yet deployed, returns the address it will be deployed to * not yet deployed, returns the address it will be deployed to
@@ -127,47 +78,4 @@ contract VaultRegistry {
return payable(vaultProxy); return payable(vaultProxy);
} }
/**
* @dev Returns the implementation address for a vault
*
* @param vault the address of the vault to query implementation for
* @return the address of the vault implementation
*/
function vaultExecutionModule(address vault, address owner)
external
view
returns (address)
{
return executionModule[vault][owner];
}
/**
* @dev Returns the owner of the Vault, which is the owner of the underlying ERC721 token
*
* @param vault the address of the vault to query ownership for
* @return the address of the vault owner
*/
function vaultOwner(address vault) public view returns (address) {
bytes memory context = MinimalProxyStore.getContext(vault);
if (context.length == 0) return address(0);
(address tokenCollection, uint256 tokenId) = abi.decode(
context,
(address, uint256)
);
return IERC721(tokenCollection).ownerOf(tokenId);
}
/**
* @dev Returns the lock status for a vault
*
* @param vault the address of the vault to query lock status for
* @return true if vault is locked, false otherwise
*/
function vaultLocked(address vault) public view returns (bool) {
return unlockTimestamp[vault] > block.timestamp;
}
} }

View File

@@ -1,11 +0,0 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
interface IExecutionModule {
function isAuthorized(address caller) external view returns (bool);
function isValidSignature(bytes32 hash, bytes memory signature)
external
view
returns (bytes4 magicValue);
}

View File

@@ -1,53 +1,22 @@
// SPDX-License-Identifier: UNLICENSED // SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13; pragma solidity ^0.8.13;
interface IVault { import "openzeppelin-contracts/interfaces/IERC1271.sol";
interface IVault is IERC1271 {
function executeCall( function executeCall(
address payable to, address payable to,
uint256 value, uint256 value,
bytes calldata data, bytes calldata data
bool useExecutionModule ) external payable returns (bytes memory);
) external payable;
function executeDelegateCall( function executor(address owner) external view returns (address);
address payable to, function setExecutor(address _executionModule) external;
bytes calldata data,
bool useExecutionModule
) external payable;
receive() external payable; function isLocked() external view returns (bool);
function lock(uint256 _unlockTimestamp) external;
fallback() external payable;
function onERC721Received(
address,
address,
uint256,
bytes calldata
) external returns (bytes4);
function onERC1155Received(
address,
address,
uint256,
uint256,
bytes calldata /* data */
) external returns (bytes4);
function onERC1155BatchReceived(
address,
address,
uint256[] calldata,
uint256[] calldata,
bytes calldata
) external returns (bytes4);
function owner() external view returns (address);
function isAuthorized(address caller) external view returns (bool); function isAuthorized(address caller) external view returns (bool);
function isValidSignature(bytes32 hash, bytes memory signature) function owner() external view returns (address);
external
view
returns (bytes4 magicValue);
} }

View File

@@ -1,43 +0,0 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
library Delegate {
/**
* @dev Delegates the current call to `implementation`.
*
* This function does not return to its internal call site, it will return directly to the external caller.
*
* Extracted from openzeppelin-contracts v4.6.0 (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/4e5b11919e91b18b6683b6f49a1b4fdede579969/contracts/proxy/Proxy.sol#L16-L45))
*/
function delegate(address implementation) internal {
assembly {
// Copy msg.data. We take full control of memory in this inline assembly
// block because it will not return to Solidity code. We overwrite the
// Solidity scratch pad at memory position 0.
calldatacopy(0, 0, calldatasize())
// Call the implementation.
// out and outsize are 0 because we don't know the size yet.
let result := delegatecall(
gas(),
implementation,
0,
calldatasize(),
0,
0
)
// Copy the returned data.
returndatacopy(0, 0, returndatasize())
switch result
// delegatecall returns 0 on error.
case 0 {
revert(0, returndatasize())
}
default {
return(0, returndatasize())
}
}
}
}

View File

@@ -11,7 +11,7 @@ import "../src/VaultRegistry.sol";
import "./mocks/MockERC721.sol"; import "./mocks/MockERC721.sol";
import "./mocks/MockERC1155.sol"; import "./mocks/MockERC1155.sol";
import "./mocks/MockERC20.sol"; import "./mocks/MockERC20.sol";
import "./mocks/MockExecutionModule.sol"; import "./mocks/MockExecutor.sol";
contract VaultTest is Test { contract VaultTest is Test {
MockERC721 public dummyERC721; MockERC721 public dummyERC721;
@@ -67,7 +67,7 @@ contract VaultTest is Test {
// user1 executes transaction to send ETH from vault // user1 executes transaction to send ETH from vault
vm.prank(user1); vm.prank(user1);
vault.executeCall(payable(user1), 0.1 ether, "", false); vault.executeCall(payable(user1), 0.1 ether, "");
// success! // success!
assertEq(vaultAddress.balance, 0.1 ether); assertEq(vaultAddress.balance, 0.1 ether);
@@ -96,7 +96,7 @@ contract VaultTest is Test {
Vault vault = Vault(vaultAddress); Vault vault = Vault(vaultAddress);
vm.prank(user1); vm.prank(user1);
vault.executeCall(payable(user1), 0.1 ether, "", false); vault.executeCall(payable(user1), 0.1 ether, "");
assertEq(vaultAddress.balance, 0.1 ether); assertEq(vaultAddress.balance, 0.1 ether);
assertEq(user1.balance, 0.1 ether); assertEq(user1.balance, 0.1 ether);
@@ -130,12 +130,7 @@ contract VaultTest is Test {
1 ether 1 ether
); );
vm.prank(user1); vm.prank(user1);
vault.executeCall( vault.executeCall(payable(address(dummyERC20)), 0, erc20TransferCall);
payable(address(dummyERC20)),
0,
erc20TransferCall,
false
);
assertEq(dummyERC20.balanceOf(vaultAddress), 0); assertEq(dummyERC20.balanceOf(vaultAddress), 0);
assertEq(dummyERC20.balanceOf(user1), 1 ether); assertEq(dummyERC20.balanceOf(user1), 1 ether);
@@ -164,12 +159,7 @@ contract VaultTest is Test {
1 ether 1 ether
); );
vm.prank(user1); vm.prank(user1);
vault.executeCall( vault.executeCall(payable(address(dummyERC20)), 0, erc20TransferCall);
payable(address(dummyERC20)),
0,
erc20TransferCall,
false
);
assertEq(dummyERC20.balanceOf(vaultAddress), 0); assertEq(dummyERC20.balanceOf(vaultAddress), 0);
assertEq(dummyERC20.balanceOf(user1), 1 ether); assertEq(dummyERC20.balanceOf(user1), 1 ether);
@@ -209,8 +199,7 @@ contract VaultTest is Test {
vault.executeCall( vault.executeCall(
payable(address(dummyERC1155)), payable(address(dummyERC1155)),
0, 0,
erc1155TransferCall, erc1155TransferCall
false
); );
assertEq(dummyERC1155.balanceOf(vaultAddress, 1), 0); assertEq(dummyERC1155.balanceOf(vaultAddress, 1), 0);
@@ -246,8 +235,7 @@ contract VaultTest is Test {
vault.executeCall( vault.executeCall(
payable(address(dummyERC1155)), payable(address(dummyERC1155)),
0, 0,
erc1155TransferCall, erc1155TransferCall
false
); );
assertEq(dummyERC1155.balanceOf(vaultAddress, 1), 0); assertEq(dummyERC1155.balanceOf(vaultAddress, 1), 0);
@@ -284,12 +272,7 @@ contract VaultTest is Test {
1 1
); );
vm.prank(user1); vm.prank(user1);
vault.executeCall( vault.executeCall(payable(address(dummyERC721)), 0, erc721TransferCall);
payable(address(dummyERC721)),
0,
erc721TransferCall,
false
);
assertEq(dummyERC721.balanceOf(address(vaultAddress)), 0); assertEq(dummyERC721.balanceOf(address(vaultAddress)), 0);
assertEq(dummyERC721.balanceOf(user1), 1); assertEq(dummyERC721.balanceOf(user1), 1);
@@ -321,12 +304,7 @@ contract VaultTest is Test {
1 1
); );
vm.prank(user1); vm.prank(user1);
vault.executeCall( vault.executeCall(payable(address(dummyERC721)), 0, erc721TransferCall);
payable(address(dummyERC721)),
0,
erc721TransferCall,
false
);
assertEq(dummyERC721.balanceOf(vaultAddress), 0); assertEq(dummyERC721.balanceOf(vaultAddress), 0);
assertEq(dummyERC721.balanceOf(user1), 1); assertEq(dummyERC721.balanceOf(user1), 1);
@@ -352,7 +330,7 @@ contract VaultTest is Test {
// should fail if user2 tries to use vault // should fail if user2 tries to use vault
vm.prank(user2); vm.prank(user2);
vm.expectRevert(Vault.NotAuthorized.selector); vm.expectRevert(Vault.NotAuthorized.selector);
vault.executeCall(payable(user2), 0.1 ether, "", false); vault.executeCall(payable(user2), 0.1 ether, "");
} }
function testVaultOwnershipTransfer(uint256 tokenId) public { function testVaultOwnershipTransfer(uint256 tokenId) public {
@@ -374,14 +352,14 @@ contract VaultTest is Test {
// should fail if user2 tries to use vault // should fail if user2 tries to use vault
vm.prank(user2); vm.prank(user2);
vm.expectRevert(Vault.NotAuthorized.selector); vm.expectRevert(Vault.NotAuthorized.selector);
vault.executeCall(payable(user2), 0.1 ether, "", false); vault.executeCall(payable(user2), 0.1 ether, "");
vm.prank(user1); vm.prank(user1);
tokenCollection.safeTransferFrom(user1, user2, tokenId); tokenCollection.safeTransferFrom(user1, user2, tokenId);
// should succeed now that user2 is owner // should succeed now that user2 is owner
vm.prank(user2); vm.prank(user2);
vault.executeCall(payable(user2), 0.1 ether, "", false); vault.executeCall(payable(user2), 0.1 ether, "");
assertEq(user2.balance, 0.1 ether); assertEq(user2.balance, 0.1 ether);
} }
@@ -454,12 +432,12 @@ contract VaultTest is Test {
// lock vault for 10 days // lock vault for 10 days
uint256 unlockTimestamp = block.timestamp + 10 days; uint256 unlockTimestamp = block.timestamp + 10 days;
vm.prank(user1); vm.prank(user1);
vaultRegistry.lockVault(vaultAddress, unlockTimestamp); vault.lock(unlockTimestamp);
// transaction should revert if vault is locked // transaction should revert if vault is locked
vm.prank(user1); vm.prank(user1);
vm.expectRevert(Vault.NotAuthorized.selector); vm.expectRevert(Vault.VaultLocked.selector);
vault.executeCall(payable(user1), 1 ether, "", false); vault.executeCall(payable(user1), 1 ether, "");
// signing should fail if vault is locked // signing should fail if vault is locked
bytes32 hash = keccak256("This is a signed message"); bytes32 hash = keccak256("This is a signed message");
@@ -473,7 +451,7 @@ contract VaultTest is Test {
// transaction succeed now that vault lock has expired // transaction succeed now that vault lock has expired
vm.prank(user1); vm.prank(user1);
vault.executeCall(payable(user1), 1 ether, "", false); vault.executeCall(payable(user1), 1 ether, "");
assertEq(user1.balance, 1 ether); assertEq(user1.balance, 1 ether);
// signing should now that vault lock has expired // signing should now that vault lock has expired
@@ -502,18 +480,12 @@ contract VaultTest is Test {
Vault vault = Vault(vaultAddress); Vault vault = Vault(vaultAddress);
MockExecutionModule mockExecutionModule = new MockExecutionModule(); MockExecutor mockExecutor = new MockExecutor();
vm.prank(user1); vm.prank(user1);
vaultRegistry.setExecutionModule( vault.setExecutor(address(mockExecutor));
vaultAddress,
address(mockExecutionModule)
);
// execution module overrides authorization check
assertTrue(vault.isAuthorized(vm.addr(2)));
// execution module handles fallback calls // execution module handles fallback calls
assertEq(MockExecutionModule(vaultAddress).customFunction(), 12345); assertEq(MockExecutor(vaultAddress).customFunction(), 12345);
} }
} }

View File

@@ -1,23 +0,0 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "openzeppelin-contracts/interfaces/IERC1271.sol";
import "../../src/interfaces/IExecutionModule.sol";
contract MockExecutionModule is IExecutionModule {
function isAuthorized(address) external pure returns (bool) {
return true;
}
function isValidSignature(bytes32, bytes memory)
external
pure
returns (bytes4 magicValue)
{
return IERC1271.isValidSignature.selector;
}
function customFunction() external pure returns (uint256) {
return 12345;
}
}

View File

@@ -0,0 +1,10 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "openzeppelin-contracts/interfaces/IERC1271.sol";
contract MockExecutor {
function customFunction() external pure returns (uint256) {
return 12345;
}
}