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

POC: Seaport bulk signatures support in Account.isValidSignature #618

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,15 @@
[submodule "lib/dynamic-contracts"]
path = lib/dynamic-contracts
url = https://github.com/thirdweb-dev/dynamic-contracts
[submodule "lib/seaport-sol"]
path = lib/seaport-sol
url = https://github.com/ProjectOpenSea/seaport-sol
[submodule "lib/murky"]
path = lib/murky
url = https://github.com/dmfxyz/murky
[submodule "lib/seaport-types"]
path = lib/seaport-types
url = https://github.com/ProjectOpenSea/seaport-types
[submodule "lib/seaport-core"]
path = lib/seaport-core
url = https://github.com/ProjectOpenSea/seaport-core
109 changes: 109 additions & 0 deletions contracts/extension/SeaportOrderEIP1271.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.11;

import { ERC1271 } from "../eip/ERC1271.sol";
import { SeaportOrderParser } from "./SeaportOrderParser.sol";
import { OrderParameters } from "seaport-types/src/lib/ConsiderationStructs.sol";
import { IAccountPermissions, AccountPermissionsStorage, EnumerableSet, ECDSA } from "./upgradeable/AccountPermissions.sol";

contract SeaportOrderEIP1271 is SeaportOrderParser, ERC1271 {
using ECDSA for bytes32;
using EnumerableSet for EnumerableSet.AddressSet;

bytes32 private constant MSG_TYPEHASH = keccak256("AccountMessage(bytes message)");
bytes32 private constant TYPEHASH =
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
bytes32 private immutable HASHED_NAME = keccak256("Account");
bytes32 private immutable HASHED_VERSION = keccak256("1");

/**
* @notice See EIP-1271
*
* @param _hash The original message hash of the data to sign (before mixing this contract's domain separator)
* @param _signature The signature produced on signing the typed data hash (result of `getMessageHash(abi.encode(rawData))`)
*/
function isValidSignature(
bytes32 _hash,
bytes memory _signature
) public view virtual override returns (bytes4 magicValue) {
bytes32 targetHash;
bytes memory targetSig;

// Handle OpenSea bulk order signatures that are >65 bytes in length.
if (_signature.length > 65) {
// Decode packed signature and order parameters.
(bytes memory extractedPackedSig, OrderParameters memory orderParameters, uint256 counter) = abi.decode(
_signature,
(bytes, OrderParameters, uint256)
);

// Verify that the original digest matches the digest built with order parameters.
bytes32 domainSeparator = _buildSeaportDomainSeparator(msg.sender);
bytes32 orderHash = _deriveOrderHash(orderParameters, counter);

require(
_deriveEIP712Digest(domainSeparator, orderHash) == _hash,
"Seaport: order hash does not match the provided message."
);

// Build bulk order hash
targetHash = _deriveEIP712Digest(domainSeparator, _computeBulkOrderProof(extractedPackedSig, orderHash));
// Extract the signature, which is the first 65 bytes
targetSig = new bytes(65);
for (uint i = 0; i < 65; i++) {
targetSig[i] = extractedPackedSig[i];
}
} else {
targetHash = _hash;
targetSig = _signature;

Check warning on line 58 in contracts/extension/SeaportOrderEIP1271.sol

View check run for this annotation

Codecov / codecov/patch

contracts/extension/SeaportOrderEIP1271.sol#L57-L58

Added lines #L57 - L58 were not covered by tests
}
bytes32 typedDataHash = keccak256(abi.encode(MSG_TYPEHASH, targetHash));
bytes32 targetDigest = keccak256(abi.encodePacked("\x19\x01", _buildDomainSeparator(), typedDataHash));

address signer = targetDigest.recover(targetSig);
AccountPermissionsStorage.Data storage data = AccountPermissionsStorage.data();

if (data.isAdmin[signer]) {
return MAGICVALUE;
}

address caller = msg.sender;
EnumerableSet.AddressSet storage approvedTargets = data.approvedTargets[signer];

Check warning on line 71 in contracts/extension/SeaportOrderEIP1271.sol

View check run for this annotation

Codecov / codecov/patch

contracts/extension/SeaportOrderEIP1271.sol#L70-L71

Added lines #L70 - L71 were not covered by tests

require(

Check warning on line 73 in contracts/extension/SeaportOrderEIP1271.sol

View check run for this annotation

Codecov / codecov/patch

contracts/extension/SeaportOrderEIP1271.sol#L73

Added line #L73 was not covered by tests
approvedTargets.contains(caller) || (approvedTargets.length() == 1 && approvedTargets.at(0) == address(0)),
"Account: caller not approved target."
);

if (isActiveSigner(signer)) {
magicValue = MAGICVALUE;

Check warning on line 79 in contracts/extension/SeaportOrderEIP1271.sol

View check run for this annotation

Codecov / codecov/patch

contracts/extension/SeaportOrderEIP1271.sol#L78-L79

Added lines #L78 - L79 were not covered by tests
}
}

/**
* @notice Returns the hash of message that should be signed for EIP1271 verification.
* @param _message The raw abi encoded data to hash and sign i.e. `abi.encode(data)`
* @return Hashed message
*/
function getMessageHash(bytes memory _message) public view returns (bytes32) {
bytes32 messageHash = keccak256(_message);
bytes32 typedDataHash = keccak256(abi.encode(MSG_TYPEHASH, messageHash));
return keccak256(abi.encodePacked("\x19\x01", _buildDomainSeparator(), typedDataHash));

Check warning on line 91 in contracts/extension/SeaportOrderEIP1271.sol

View check run for this annotation

Codecov / codecov/patch

contracts/extension/SeaportOrderEIP1271.sol#L89-L91

Added lines #L89 - L91 were not covered by tests
}

/// @notice Returns whether the given account is an active signer on the account.
function isActiveSigner(address signer) public view returns (bool) {
IAccountPermissions.SignerPermissionsStatic memory permissions = AccountPermissionsStorage

Check warning on line 96 in contracts/extension/SeaportOrderEIP1271.sol

View check run for this annotation

Codecov / codecov/patch

contracts/extension/SeaportOrderEIP1271.sol#L96

Added line #L96 was not covered by tests
.data()
.signerPermissions[signer];

return
permissions.startTimestamp <= block.timestamp &&
block.timestamp < permissions.endTimestamp &&
AccountPermissionsStorage.data().approvedTargets[signer].length() > 0;

Check warning on line 103 in contracts/extension/SeaportOrderEIP1271.sol

View check run for this annotation

Codecov / codecov/patch

contracts/extension/SeaportOrderEIP1271.sol#L100-L103

Added lines #L100 - L103 were not covered by tests
}

function _buildDomainSeparator() private view returns (bytes32) {
return keccak256(abi.encode(TYPEHASH, HASHED_NAME, HASHED_VERSION, block.chainid, address(this)));
}
}
Loading
Loading