Skip to content

Commit

Permalink
Implement EIP-1271 with Access Control for IP Account (#362)
Browse files Browse the repository at this point in the history
  • Loading branch information
kingster-will authored Dec 13, 2024
1 parent cb033b7 commit 1505d79
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 0 deletions.
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

0 comments on commit 1505d79

Please sign in to comment.