Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement updates from internal review #9

Merged
merged 13 commits into from
Feb 1, 2024
64 changes: 32 additions & 32 deletions README.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ src = 'src'
out = 'out'
libs = ['lib']
optimizer_runs = 1_000_000
gas_reports = ["HatsWallet1ofN", "HatsWalletMofN"]
gas_reports = ["HatsAccount1ofN", "HatsAccountMofN"]
auto_detect_solc = false
solc = "0.8.23"
bytecode_hash = "none"
Expand Down
12 changes: 6 additions & 6 deletions script/HatsWallet1ofN.s.sol → script/HatsAccount1ofN.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
pragma solidity ^0.8.18;

import { Script, console2 } from "forge-std/Script.sol";
import { HatsWallet1ofN } from "../src/HatsWallet1ofN.sol";
import { HatsAccount1ofN } from "../src/HatsAccount1ofN.sol";
import { IHats } from "hats-protocol/Interfaces/IHats.sol";
import { IERC6551Registry } from "erc6551/interfaces/IERC6551Registry.sol";

contract DeployImplementation is Script {
HatsWallet1ofN public implementation;
HatsAccount1ofN public implementation;
bool private _verbose = true;
string private _version = "test1";

Expand All @@ -24,21 +24,21 @@ contract DeployImplementation is Script {

vm.startBroadcast(deployer);
// deploy the implementation
implementation = new HatsWallet1ofN{ salt: SALT }(_version);
implementation = new HatsAccount1ofN{ salt: SALT }(_version);

vm.stopBroadcast();

if (_verbose) {
console2.log("implementation", address(implementation));
}
}
// forge script script/HatsWallet1ofN.s.sol:DeployImplementation -f mainnet --broadcast --verify
// forge script script/HatsAccount1ofN.s.sol:DeployImplementation -f mainnet --broadcast --verify

/*
forge verify-contract --chain-id 5 --num-of-optimizations 1000000 --watch --constructor-args $(cast abi-encode \
"constructor(string)" "test1" ) \
--compiler-version v0.8.21 0x009702D64E366cA4E4b0c72a72c9d16cB7e2A728 \
src/HatsWallet1ofN.sol:HatsWallet1ofN --etherscan-api-key $ETHERSCAN_KEY
src/HatsAccount1ofN.sol:HatsAccount1ofN --etherscan-api-key $ETHERSCAN_KEY
*/
}

Expand Down Expand Up @@ -77,5 +77,5 @@ contract DeployWallet is Script {
return wallet;
}

// forge script script/HatsWallet.s.sol:DeployWallet -f goerli
// forge script script/HatsAccount.s.sol:DeployWallet -f goerli
}
12 changes: 6 additions & 6 deletions script/HatsWalletMofN.s.sol → script/HatsAccountMofN.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
pragma solidity ^0.8.18;

import { Script, console2 } from "forge-std/Script.sol";
import { HatsWalletMofN } from "../src/HatsWalletMofN.sol";
import { HatsAccountMofN } from "../src/HatsAccountMofN.sol";
import { IHats } from "hats-protocol/Interfaces/IHats.sol";
import { IERC6551Registry } from "erc6551/interfaces/IERC6551Registry.sol";

contract DeployImplementation is Script {
HatsWalletMofN public implementation;
HatsAccountMofN public implementation;
bool private _verbose = true;
string private _version = "test1";

Expand All @@ -24,20 +24,20 @@ contract DeployImplementation is Script {

vm.startBroadcast(deployer);
// deploy the implementation
implementation = new HatsWalletMofN{ salt: SALT }(_version);
implementation = new HatsAccountMofN{ salt: SALT }(_version);

vm.stopBroadcast();
if (_verbose) {
console2.log("implementation", address(implementation));
}
}
// forge script script/HatsWallet.s.sol:DeployImplementation -f mainnet --broadcast --verify
// forge script script/HatsAccount.s.sol:DeployImplementation -f mainnet --broadcast --verify

/*
forge verify-contract --chain-id 5 --num-of-optimizations 1000000 --watch --constructor-args $(cast abi-encode \
"constructor(string)" "test1" ) \
--compiler-version v0.8.21 0xEA95A8Da1746897343c56f5468489a36BbC5e0Bc \
src/HatsWalletMofN.sol:HatsWalletMofN --etherscan-api-key $ETHERSCAN_KEY
src/HatsAccountMofN.sol:HatsAccountMofN --etherscan-api-key $ETHERSCAN_KEY
*/
}

Expand Down Expand Up @@ -76,5 +76,5 @@ contract DeployWallet is Script {
return wallet;
}

// forge script script/HatsWallet.s.sol:DeployWallet -f goerli
// forge script script/HatsAccount.s.sol:DeployWallet -f goerli
}
38 changes: 19 additions & 19 deletions src/HatsWallet1ofN.sol → src/HatsAccount1ofN.sol
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

// import { console2, Test } from "forge-std/Test.sol"; // remove before deploy
import "./lib/HatsWalletErrors.sol";
import { HatsWalletBase } from "./HatsWalletBase.sol";
import { LibHatsWallet, Operation } from "./lib/LibHatsWallet.sol";
// import { console2, Test } from "forge-std/Test.sol"; // comment out before deploy
import "./lib/HatsAccountErrors.sol";
import { HatsAccountBase } from "./HatsAccountBase.sol";
import { LibHatsAccount, Operation } from "./lib/LibHatsAccount.sol";
import { IERC1271 } from "@openzeppelin/contracts/interfaces/IERC1271.sol";
import { ECDSA } from "solady/utils/ECDSA.sol";
import { SignatureCheckerLib } from "solady/utils/SignatureCheckerLib.sol";
import { IERC6551Executable } from "erc6551/interfaces/IERC6551Executable.sol";

/**
* @title HatsWallet1ofN
* @title HatsAccount1ofN
* @author Haberdasher Labs
* @author spengrah
* @notice A HatsWallet implementation that requires a single signature from a valid signer — ie a single wearer of
* the hat — to execute a transaction. It supports execution of single as batch operations, as well as EIP-1271
* @notice A HatsAccount implementation that requires a single signature from a valid signer — ie a single wearer of
* the hat — to execute a transaction. It supports execution of single operations, batch operations, and EIP-1271
* contract signatures.
*/
contract HatsWallet1ofN is HatsWalletBase, IERC6551Executable {
contract HatsAccount1ofN is HatsAccountBase, IERC6551Executable {
/*///////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
Expand All @@ -31,6 +30,7 @@ contract HatsWallet1ofN is HatsWalletBase, IERC6551Executable {
//////////////////////////////////////////////////////////////*/

constructor(string memory version) {
// set the implementation version
_version = version;
}

Expand All @@ -54,8 +54,9 @@ contract HatsWallet1ofN is HatsWalletBase, IERC6551Executable {
_beforeExecute();

// execute the call, routing delegatecalls through the sandbox, and bubble up the result
result = LibHatsWallet._execute(_to, _value, _data, _operation);
result = LibHatsAccount._execute(_to, _value, _data, _operation);

// log the executor
emit TxExecuted(msg.sender);
}

Expand All @@ -74,15 +75,14 @@ contract HatsWallet1ofN is HatsWalletBase, IERC6551Executable {
uint256 length = operations.length;
bytes[] memory results = new bytes[](length);

for (uint256 i; i < length;) {
for (uint256 i; i < length; ++i) {
/// @dev compile with solc ^0.8.23 to use unchecked incrementation
// execute the call, routing delegatecalls through the sandbox, and bubble up the result
results[i] =
LibHatsWallet._execute(operations[i].to, operations[i].value, operations[i].data, operations[i].operation);
unchecked {
++i;
}
LibHatsAccount._execute(operations[i].to, operations[i].value, operations[i].data, operations[i].operation);
}

// log the executor
emit TxExecuted(msg.sender);

return results;
Expand All @@ -95,8 +95,8 @@ contract HatsWallet1ofN is HatsWalletBase, IERC6551Executable {
/**
* @notice Checks whether the signature provided is valid for the provided hash, complies with EIP-1271. A signature
* is valid if either:
* - It's a valid ECDSA signature by a valid HatsWallet signer
* - It's a valid EIP-1271 signature by a valid HatsWallet signer
* - It's a valid ECDSA signature by a valid HatsAccount signer
* - It's a valid EIP-1271 signature by a valid HatsAccount signer
* @dev Implementation borrowed from https://github.com/gnosis/mech/blob/main/contracts/base/Mech.sol
* @param _hash Hash of the data (could be either a message hash or transaction hash)
* @param _signature Signature to validate. Can be an EIP-1271 contract signature (identified by v=0) or an ECDSA
Expand All @@ -107,7 +107,7 @@ contract HatsWallet1ofN is HatsWalletBase, IERC6551Executable {
bytes32 r;
bytes32 s;
uint8 v;
(v, r, s) = LibHatsWallet._splitSignature(signature);
(v, r, s) = LibHatsAccount._splitSignature(signature);

if (v == 0) {
// This is an EIP-1271 contract signature
Expand Down Expand Up @@ -141,7 +141,7 @@ contract HatsWallet1ofN is HatsWalletBase, IERC6551Executable {
VIEW FUNCTIONS
//////////////////////////////////////////////////////////////*/

/// @inheritdoc HatsWalletBase
/// @inheritdoc HatsAccountBase
function supportsInterface(bytes4 interfaceId) public view override returns (bool) {
return (
interfaceId == type(IERC6551Executable).interfaceId || interfaceId == type(IERC1271).interfaceId
Expand Down
37 changes: 20 additions & 17 deletions src/HatsWalletBase.sol → src/HatsAccountBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,25 @@
pragma solidity ^0.8.19;

// import { console2, Test } from "forge-std/Test.sol"; // remove before deploy
import "./lib/HatsWalletErrors.sol";
import "./lib/HatsAccountErrors.sol";
import { IHats } from "hats-protocol/Interfaces/IHats.sol";
import { LibHatsWallet, Operation } from "./lib/LibHatsWallet.sol";
import { LibHatsAccount, Operation } from "./lib/LibHatsAccount.sol";
import { ERC6551Account, IERC165, IERC6551Account, ERC6551AccountLib } from "tokenbound/abstract/ERC6551Account.sol";
import { BaseExecutor } from "tokenbound/abstract/execution/BaseExecutor.sol";
import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import { IERC721Receiver } from "@openzeppelin/contracts/interfaces/IERC721Receiver.sol";
import { IERC1155Receiver } from "@openzeppelin/contracts/interfaces/IERC1155Receiver.sol";

/**
* @title HatsWalletBase
* @title HatsAccountBase
* @author Haberdasher Labs
* @author spengrah
* @notice The base contract for all HatsWallet implementations. As an abstract contract, this contract will only work
* when inherited by a full implementation of HatsWallet. HatsWallet is a flavor of ERC6551-compatible token-bound
* @notice The base contract for all HatsAccount implementations. As an abstract contract, this contract will only work
* when inherited by a full implementation of HatsAccount. HatsAccount is a flavor of ERC6551-compatible token-bound
* account for Hats Protocol hats.
* @dev This contract implements ERC6551 with the use of the tokenbound library.
*/
abstract contract HatsWalletBase is ERC6551Account, BaseExecutor, IERC721Receiver, IERC1155Receiver {
abstract contract HatsAccountBase is ERC6551Account, BaseExecutor, IERC721Receiver, IERC1155Receiver {
/*//////////////////////////////////////////////////////////////
CONSTANTS
//////////////////////////////////////////////////////////////*/
Expand All @@ -30,14 +30,14 @@ abstract contract HatsWalletBase is ERC6551Account, BaseExecutor, IERC721Receive
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");

/// @notice EIP-712 message typehash for this contract
bytes32 internal constant HATSWALLET_MSG_TYPEHASH = keccak256("HatsWallet(bytes message)");
bytes32 internal constant HatsAccount_MSG_TYPEHASH = keccak256("HatsAccount(bytes message)");

/// @notice The salt used to create this HatsWallet instance
/// @notice The salt used to create this HatsAccount instance
function salt() public view returns (bytes32) {
return ERC6551AccountLib.salt();
}

/// @notice The Hats Protocol hat whose wearer controls this HatsWallet
/// @notice The Hats Protocol hat whose wearer controls this HatsAccount
function hat() public view returns (uint256) {
bytes memory footer = new bytes(0x20);
assembly {
Expand All @@ -57,26 +57,28 @@ abstract contract HatsWalletBase is ERC6551Account, BaseExecutor, IERC721Receive
return abi.decode(footer, (IHats));
}

/// @notice The address of this HatsWallet implementation
/// @notice The address of this HatsAccount implementation
function IMPLEMENTATION() public view returns (address) {
return ERC6551AccountLib.implementation();
}

/// @notice The version of the HatsWallet implementation contract
/// @dev Will return an empty string when called on a HatsWallet instance (clone)
/// @notice The version of the HatsAccount implementation contract
/// @dev Will return an empty string when called on a HatsAccount instance (clone)
function version_() public view returns (string memory) {
return _version;
}

/// @notice The version of this HatsWallet instance (clone)
/// @notice The version of this HatsAccount instance (clone)
function version() public view returns (string memory) {
return HatsWalletBase(payable(IMPLEMENTATION())).version_();
return HatsAccountBase(payable(IMPLEMENTATION())).version_();
}

/*//////////////////////////////////////////////////////////////
NON-CONSTANT STORAGE
//////////////////////////////////////////////////////////////*/

/// @dev The version of this HatsAccount implementation contract. Must be set in the constructor of the inheriting
/// contract. Will be empty for HatsAccount instances (clones).
string internal _version;

/*//////////////////////////////////////////////////////////////
Expand All @@ -94,7 +96,7 @@ abstract contract HatsWalletBase is ERC6551Account, BaseExecutor, IERC721Receive
bytes1(0x19),
bytes1(0x01),
domainSeparator(),
keccak256(abi.encode(HATSWALLET_MSG_TYPEHASH, keccak256(_message))) // HatsWalletMessageHash
keccak256(abi.encode(HatsAccount_MSG_TYPEHASH, keccak256(_message))) // HatsAccountMessageHash
)
);
}
Expand All @@ -118,13 +120,14 @@ abstract contract HatsWalletBase is ERC6551Account, BaseExecutor, IERC721Receive
//////////////////////////////////////////////////////////////*/

/// @inheritdoc ERC6551Account
/// @dev HatsAccount signer validation does not require additional context data
function _isValidSigner(address _signer, bytes memory /* context */ ) internal view override returns (bool) {
return _isValidSigner(_signer);
}

/**
* @dev Internal function to check if a given address is a valid signer for this HatsWallet. A signer is valid if they
* are wearing the `hat` of this HatsWallet.
* @dev Internal function to check if a given address is a valid signer for this HatsAccount. A signer is valid if and
* only if they are wearing the `hat` of this HatsAccount instance.
* @param _signer The address to check
*/
function _isValidSigner(address _signer) internal view returns (bool) {
Expand Down
Loading
Loading