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 EIP-1271 with Access Control for IP Account #362

Merged
merged 2 commits into from
Dec 13, 2024
Merged
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
30 changes: 30 additions & 0 deletions contracts/IPAccountImpl.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { IERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol"
import { IERC721Receiver } from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import { IERC1155Receiver } from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol";
import { SignatureChecker } from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import { MessageHashUtils } from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
import { IERC6551Account } from "erc6551/interfaces/IERC6551Account.sol";
import { IERC6551Executable } from "erc6551/interfaces/IERC6551Executable.sol";
Expand Down Expand Up @@ -248,6 +249,35 @@ contract IPAccountImpl is ERC6551, IPAccountStorage, IIPAccount {
return isValidSigner(signer, address(uint160(uint256(extraData))), context);
}

/// @dev Returns whether the `signature` is valid for the `hash.
function _erc1271IsValidSignature(bytes32 hash, bytes calldata signature) internal view override returns (bool) {
uint8 v = uint8(signature[64]);
address signer;

// Smart contract signature
if (v == 0) {
// Signer address encoded in r
signer = address(uint160(uint256(bytes32(signature[:32]))));

// Allow recursive signature verification
if (!this.isValidSigner(signer, address(0), "") && signer != address(this)) {
return false;
}

// Signature offset encoded in s
bytes calldata _signature = signature[uint256(bytes32(signature[32:64])):];

return SignatureChecker.isValidERC1271SignatureNow(signer, hash, _signature);
}

ECDSA.RecoverError _error;
(signer, _error, ) = ECDSA.tryRecover(hash, signature);

if (_error != ECDSA.RecoverError.NoError) return false;

return this.isValidSigner(signer, address(0), "");
}

/// @dev Override Solady EIP712 function and return EIP712 domain name for IPAccount.
function _domainNameAndVersion() internal view override returns (string memory name, string memory version) {
name = "Story Protocol IP Account";
Expand Down
72 changes: 72 additions & 0 deletions test/foundry/IPAccountMetaTx.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
pragma solidity 0.8.26;

import { MessageHashUtils } from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
import { ERC6551 } from "@solady/src/accounts/ERC6551.sol";
import { ERC1271 } from "@solady/src/accounts/ERC1271.sol";

import { IIPAccount } from "../../contracts/interfaces/IIPAccount.sol";
import { MetaTx } from "../../contracts/lib/MetaTx.sol";
Expand Down Expand Up @@ -188,6 +190,76 @@ contract IPAccountMetaTxTest is BaseTest {
assertEq(ipAccount.state(), expectedState);
}

function test_IPAccount_isValidSignature() public {
uint256 tokenId = 100;

mockNFT.mintId(owner, tokenId);

address account = ipAssetRegistry.register(block.chainid, address(mockNFT), tokenId);

IIPAccount ipAccount = IIPAccount(payable(account));

uint deadline = block.timestamp + 1000;

bytes32 setPermissionState = keccak256(
abi.encode(
ipAccount.state(),
abi.encodeWithSignature(
"execute(address,uint256,bytes)",
address(accessController),
0,
abi.encodeWithSignature(
"setPermission(address,address,address,bytes4,uint8)",
address(ipAccount),
address(metaTxModule),
address(module),
bytes4(0),
AccessPermission.ALLOW
)
)
)
);
bytes32 expectedState = keccak256(
abi.encode(
setPermissionState,
abi.encodeWithSignature(
"execute(address,uint256,bytes)",
address(module),
0,
abi.encodeWithSignature("executeSuccessfully(string)", "test")
)
)
);

bytes32 digest = MessageHashUtils.toTypedDataHash(
MetaTx.calculateDomainSeparator(address(ipAccount)),
MetaTx.getExecuteStructHash(
MetaTx.Execute({
to: address(accessController),
value: 0,
data: abi.encodeWithSignature(
"setPermission(address,address,address,bytes4,uint8)",
address(ipAccount),
address(metaTxModule),
address(module),
bytes4(0),
AccessPermission.ALLOW
),
nonce: setPermissionState,
deadline: deadline
})
)
);

(uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPrivateKey, digest);

bytes memory signature = abi.encodePacked(r, s, v);
assertEq(
ERC6551(payable(address(ipAccount))).isValidSignature(digest, signature),
ERC1271.isValidSignature.selector
);
}

function test_IPAccount_setPermissionWithSignatureThenCallAccessControlledModule() public {
uint256 tokenId = 100;

Expand Down
Loading