Skip to content

Commit

Permalink
Wallet Linking via EIP-712 (#189)
Browse files Browse the repository at this point in the history
Co-authored-by: Tak Wai Wong <[email protected]>
  • Loading branch information
giuseppecrj and tak-hntlabs authored Jul 1, 2024
1 parent ec1ee32 commit 425fb68
Show file tree
Hide file tree
Showing 119 changed files with 15,092 additions and 543 deletions.
10 changes: 10 additions & 0 deletions contracts/scripts/deployments/DeploySpaceFactory.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {DeployPricingModules} from "contracts/scripts/deployments/facets/DeployP
import {DeployImplementationRegistry} from "contracts/scripts/deployments/facets/DeployImplementationRegistry.s.sol";
import {DeployPausable} from "contracts/scripts/deployments/facets/DeployPausable.s.sol";
import {DeployPlatformRequirements} from "./facets/DeployPlatformRequirements.s.sol";
import {DeployEIP712Facet} from "contracts/scripts/deployments/facets/DeployEIP712Facet.s.sol";

contract DeploySpaceFactory is DiamondDeployer {
// diamond helpers
Expand Down Expand Up @@ -65,6 +66,7 @@ contract DeploySpaceFactory is DiamondDeployer {
DeployFixedPricing deployFixedPricing = new DeployFixedPricing();
DeployPlatformRequirements platformReqsHelper =
new DeployPlatformRequirements();
DeployEIP712Facet eip712Helper = new DeployEIP712Facet();

// helpers

Expand All @@ -83,6 +85,7 @@ contract DeploySpaceFactory is DiamondDeployer {

address registry;
address walletLink;
address eip712;

// external contracts
address public userEntitlement;
Expand Down Expand Up @@ -133,6 +136,8 @@ contract DeploySpaceFactory is DiamondDeployer {
pausable = pausableHelper.deploy();
platformReqs = platformReqsHelper.deploy();

eip712 = eip712Helper.deploy();

addFacet(
diamondCutHelper.makeCut(diamondCut, IDiamond.FacetCutAction.Add),
diamondCut,
Expand Down Expand Up @@ -206,6 +211,11 @@ contract DeploySpaceFactory is DiamondDeployer {
walletLink,
walletLinkHelper.makeInitData("")
);
addFacet(
eip712Helper.makeCut(eip712, IDiamond.FacetCutAction.Add),
eip712,
eip712Helper.makeInitData("SpaceFactory", "1")
);

return
Diamond.InitParams({
Expand Down
39 changes: 39 additions & 0 deletions contracts/scripts/deployments/facets/DeployEIP712Facet.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

//interfaces

//libraries

//contracts
import {Deployer} from "contracts/scripts/common/Deployer.s.sol";
import {FacetHelper} from "contracts/test/diamond/Facet.t.sol";
import {EIP712Facet} from "contracts/src/diamond/utils/cryptography/signature/EIP712Facet.sol";

contract DeployEIP712Facet is FacetHelper, Deployer {
constructor() {
addSelector(EIP712Facet.eip712Domain.selector);
}

function versionName() public pure override returns (string memory) {
return "eip712Facet";
}

function initializer() public pure override returns (bytes4) {
return EIP712Facet.__EIP712_init.selector;
}

function makeInitData(
string memory name,
string memory version
) public pure returns (bytes memory) {
return abi.encodeWithSelector(initializer(), name, version);
}

function __deploy(address deployer) public override returns (address) {
vm.startBroadcast(deployer);
EIP712Facet facet = new EIP712Facet();
vm.stopBroadcast();
return address(facet);
}
}
157 changes: 157 additions & 0 deletions contracts/src/diamond/utils/cryptography/signature/EIP712Base.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.23;

// interfaces

// libraries
import {EIP712Storage} from "./EIP712Storage.sol";
import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";

// contracts

/**
* @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data.
*
* The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible,
* thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding
* they need in their contracts using a combination of `abi.encode` and `keccak256`.
*
* This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
* scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
* ({_hashTypedDataV4}).
*
* The implementation of the domain separator was designed to be as efficient as possible while still properly updating
* the chain id to protect against replay attacks on an eventual fork of the chain.
*
* NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
* https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
*/
abstract contract EIP712Base {
using EIP712Storage for EIP712Storage.Layout;

bytes32 private constant _TYPE_HASH =
keccak256(
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
);

function __EIP712_init_unchained(
string memory name,
string memory version
) internal {
EIP712Storage.Layout storage dl = EIP712Storage.layout();

dl.name = name;
dl.version = version;

// Reset prior values in storage if upgrading
dl.hashedName = 0;
dl.hashedVersion = 0;
}

/**
* @dev Returns the domain separator for the current chain.
*/
function _domainSeparatorV4() internal view returns (bytes32) {
return _buildDomainSeparator();
}

/**
* @dev Builds the domain separator for the current chain.
*/
function _buildDomainSeparator() private view returns (bytes32) {
return
keccak256(
abi.encode(
_TYPE_HASH,
_EIP712NameHash(),
_EIP712VersionHash(),
block.chainid,
address(this)
)
);
}

/**
* @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
* function returns the hash of the fully encoded EIP712 message for this domain.
*
* This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
*
* ```solidity
* bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
* keccak256("Mail(address to,string contents)"),
* mailTo,
* keccak256(bytes(mailContents))
* )));
* address signer = ECDSA.recover(digest, signature);
* ```
*/
function _hashTypedDataV4(
bytes32 structHash
) internal view virtual returns (bytes32) {
return MessageHashUtils.toTypedDataHash(_domainSeparatorV4(), structHash);
}

/**
* @dev The name parameter for the EIP712 domain.
*
* NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs
* are a concern.
*/
function _EIP712Name() internal view virtual returns (string memory) {
return EIP712Storage.layout().name;
}

/**
* @dev The version parameter for the EIP712 domain.
*
* NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs
* are a concern.
*/
function _EIP712Version() internal view virtual returns (string memory) {
return EIP712Storage.layout().version;
}

/**
* @dev The hash of the name parameter for the EIP712 domain.
*
* NOTE: In previous versions this function was virtual. In this version you should override `_EIP712Name` instead.
*/
function _EIP712NameHash() internal view returns (bytes32) {
string memory name = _EIP712Name();
if (bytes(name).length > 0) {
return keccak256(bytes(name));
} else {
// If the name is empty, the contract may have been upgraded without initializing the new storage.
// We return the name hash in storage if non-zero, otherwise we assume the name is empty by design.
bytes32 hashedName = EIP712Storage.layout().hashedName;
if (hashedName != 0) {
return hashedName;
} else {
return keccak256("");
}
}
}

/**
* @dev The hash of the version parameter for the EIP712 domain.
*
* NOTE: In previous versions this function was virtual. In this version you should override `_EIP712Version` instead.
*/
function _EIP712VersionHash() internal view returns (bytes32) {
string memory version = _EIP712Version();
if (bytes(version).length > 0) {
return keccak256(bytes(version));
} else {
// If the version is empty, the contract may have been upgraded without initializing the new storage.
// We return the version hash in storage if non-zero, otherwise we assume the version is empty by design.
bytes32 hashedVersion = EIP712Storage.layout().hashedVersion;
if (hashedVersion != 0) {
return hashedVersion;
} else {
return keccak256("");
}
}
}
}
57 changes: 57 additions & 0 deletions contracts/src/diamond/utils/cryptography/signature/EIP712Facet.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.23;

// interfaces
import {IERC5267} from "../IERC5267.sol";

// libraries
import {EIP712Storage} from "./EIP712Storage.sol";

// contracts
import {EIP712Base} from "./EIP712Base.sol";
import {Nonces} from "contracts/src/diamond/utils/Nonces.sol";
import {Facet} from "contracts/src/diamond/facets/Facet.sol";

contract EIP712Facet is IERC5267, EIP712Base, Nonces, Facet {
function __EIP712_init(
string calldata name,
string calldata version
) external onlyInitializing {
__EIP712_init_unchained(name, version);
}

function eip712Domain()
public
view
virtual
override
returns (
bytes1 fields,
string memory name,
string memory version,
uint256 chainId,
address verifyingContract,
bytes32 salt,
uint256[] memory extensions
)
{
EIP712Storage.Layout storage dl = EIP712Storage.layout();

// If the hashed name and version in storage are non-zero, the contract hasn't been properly initialized
// and the EIP712 domain is not reliable, as it will be missing name and version.
require(
dl.hashedName == 0 && dl.hashedVersion == 0,
"EIP712: Uninitialized"
);

return (
hex"0f", // 01111
dl.name,
dl.version,
block.chainid,
address(this),
bytes32(0),
new uint256[](0)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

// interfaces

// libraries

// contracts

library EIP712Storage {
struct Layout {
bytes32 hashedName;
bytes32 hashedVersion;
string name;
string version;
}

// keccak256(abi.encode(uint256(keccak256("diamond.utils.cryptography.eip712.storage")) - 1)) & ~bytes32(uint256(0xff))
bytes32 internal constant STORAGE_SLOT =
0x219639d1c7dec7d049ffb8dc11e39f070f052764b142bd61682a7811a502a600;

function layout() internal pure returns (Layout storage l) {
bytes32 slot = STORAGE_SLOT;
assembly {
l.slot := slot
}
}
}
1 change: 1 addition & 0 deletions contracts/src/factory/facets/wallet-link/IWalletLink.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ interface IWalletLinkBase {
struct LinkedWallet {
address addr;
bytes signature;
string message;
}

// =============================================================
Expand Down
Loading

0 comments on commit 425fb68

Please sign in to comment.