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

Integration with Solday's ERC6551 Implementation #133

Merged
merged 5 commits into from
Apr 25, 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
144 changes: 89 additions & 55 deletions contracts/IPAccountImpl.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
pragma solidity 0.8.23;

import { IERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.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 { MessageHashUtils } from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
import { IERC6551Account } from "erc6551/interfaces/IERC6551Account.sol";
import { ERC6551, Receiver } from "@solady/src/accounts/ERC6551.sol";

import { IAccessController } from "./interfaces/access/IAccessController.sol";
import { IIPAccount } from "./interfaces/IIPAccount.sol";
Expand All @@ -17,13 +17,10 @@ import { IPAccountStorage } from "./IPAccountStorage.sol";

/// @title IPAccountImpl
/// @notice The Story Protocol's implementation of the IPAccount.
contract IPAccountImpl is IPAccountStorage, IIPAccount {
contract IPAccountImpl is ERC6551, IPAccountStorage, IIPAccount {
address public immutable ACCESS_CONTROLLER;

/// @notice Returns the IPAccount's internal nonce for transaction ordering.
uint256 public state;

receive() external payable override(IERC6551Account) {}
receive() external payable override(Receiver, IIPAccount) {}

/// @notice Creates a new IPAccountImpl contract instance
/// @dev Initializes the IPAccountImpl with an AccessController address which is stored
Expand All @@ -44,7 +41,9 @@ contract IPAccountImpl is IPAccountStorage, IIPAccount {
/// @notice Checks if the contract supports a specific interface
/// @param interfaceId The interface identifier, as specified in ERC-165
/// @return bool is true if the contract supports the interface, false otherwise
function supportsInterface(bytes4 interfaceId) public view override(IPAccountStorage, IERC165) returns (bool) {
function supportsInterface(
bytes4 interfaceId
) public view override(ERC6551, IPAccountStorage, IERC165) returns (bool) {
return (interfaceId == type(IIPAccount).interfaceId ||
interfaceId == type(IERC6551Account).interfaceId ||
interfaceId == type(IERC1155Receiver).interfaceId ||
Expand All @@ -56,42 +55,30 @@ contract IPAccountImpl is IPAccountStorage, IIPAccount {
/// @return chainId The EIP-155 ID of the chain the token exists on
/// @return tokenContract The contract address of the token
/// @return tokenId The ID of the token
function token() public view override returns (uint256, address, uint256) {
bytes memory footer = new bytes(0x60);
// 0x4d = 77 bytes (ERC-1167 Header, address, ERC-1167 Footer, salt)
// 0x60 = 96 bytes (chainId, tokenContract, tokenId)
// ERC-1167 Header (10 bytes)
// <implementation (address)> (20 bytes)
// ERC-1167 Footer (15 bytes)
// <salt (uint256)> (32 bytes)
// <chainId (uint256)> (32 bytes)
// <tokenContract (address)> (32 bytes)
// <tokenId (uint256)> (32 bytes)
assembly {
extcodecopy(address(), add(footer, 0x20), 0x4d, 0x60)
}

return abi.decode(footer, (uint256, address, uint256));
function token() public view override(ERC6551, IIPAccount) returns (uint256, address, uint256) {
return super.token();
}

/// @notice Checks if the signer is valid for the given data
/// @param signer The signer to check
/// @param data The data to check against
/// @return The function selector if the signer is valid, 0 otherwise
function isValidSigner(address signer, bytes calldata data) external view returns (bytes4) {
if (_isValidSigner(signer, address(0), data)) {
return IERC6551Account.isValidSigner.selector;
}

return bytes4(0);
function isValidSigner(
address signer,
bytes calldata data
) public view override(ERC6551, IIPAccount) returns (bytes4) {
return super.isValidSigner(signer, data);
}

/// @notice Returns the owner of the IP Account.
/// @return The address of the owner.
function owner() public view returns (address) {
(uint256 chainId, address contractAddress, uint256 tokenId) = token();
if (chainId != block.chainid) return address(0);
LeoHChen marked this conversation as resolved.
Show resolved Hide resolved
return IERC721(contractAddress).ownerOf(tokenId);
function owner() public view override(ERC6551, IIPAccount) returns (address) {
return super.owner();
}

/// @notice Returns the IPAccount's internal nonce for transaction ordering.
function state() public view override(ERC6551, IIPAccount) returns (bytes32 result) {
return super.state();
}

/// @dev Checks if the signer is valid for the given data and recipient via the AccessController permission system.
Expand Down Expand Up @@ -135,12 +122,12 @@ contract IPAccountImpl is IPAccountStorage, IIPAccount {
revert Errors.IPAccount__ExpiredSignature();
}

++state;
_updateStateForExecute(to, value, data);

bytes32 digest = MessageHashUtils.toTypedDataHash(
MetaTx.calculateDomainSeparator(),
MetaTx.getExecuteStructHash(
MetaTx.Execute({ to: to, value: value, data: data, nonce: state, deadline: deadline })
MetaTx.Execute({ to: to, value: value, data: data, nonce: state(), deadline: deadline })
)
);

Expand All @@ -149,7 +136,7 @@ contract IPAccountImpl is IPAccountStorage, IIPAccount {
}

result = _execute(signer, to, value, data);
emit ExecutedWithSig(to, value, data, state, deadline, signer, signature);
emit ExecutedWithSig(to, value, data, state(), deadline, signer, signature);
}

/// @notice Executes a transaction from the IP Account.
Expand All @@ -158,30 +145,45 @@ contract IPAccountImpl is IPAccountStorage, IIPAccount {
/// @param data The data to send along with the transaction.
/// @return result The return data from the transaction.
function execute(address to, uint256 value, bytes calldata data) external payable returns (bytes memory result) {
++state;
_updateStateForExecute(to, value, data);
result = _execute(msg.sender, to, value, data);
emit Executed(to, value, data, state);
emit Executed(to, value, data, state());
}

/// @inheritdoc IERC721Receiver
function onERC721Received(address, address, uint256, bytes memory) public pure returns (bytes4) {
return this.onERC721Received.selector;
}

/// @inheritdoc IERC1155Receiver
function onERC1155Received(address, address, uint256, uint256, bytes memory) public pure returns (bytes4) {
return this.onERC1155Received.selector;
/// @dev Override 6551 execute function.
/// Only "CALL" operation is supported.
/// @param to The recipient of the transaction.
/// @param value The amount of Ether to send.
/// @param data The data to send along with the transaction.
/// @param operation The operation type to perform, only 0 - CALL is supported.
/// @return result The return data from the transaction.
function execute(
address to,
uint256 value,
bytes calldata data,
uint8 operation
) public payable override returns (bytes memory result) {
// Only "CALL" operation is supported.
if (operation != 0) {
revert Errors.IPAccount__InvalidOperation();
}
_updateStateForExecute(to, value, data);
result = _execute(msg.sender, to, value, data);
emit Executed(to, value, data, state());
}

/// @inheritdoc IERC1155Receiver
function onERC1155BatchReceived(
address,
address,
uint256[] memory,
uint256[] memory,
bytes memory
) public pure returns (bytes4) {
return this.onERC1155BatchReceived.selector;
/// @notice Executes a batch of transactions from the IP Account.
/// @param calls The array of calls to execute.
/// @param operation The operation type to perform, only 0 - CALL is supported.
/// @return results The return data from the transactions.
function executeBatch(
Call[] calldata calls,
uint8 operation
) public payable override returns (bytes[] memory results) {
results = new bytes[](calls.length);
for (uint256 i = 0; i < calls.length; i++) {
results[i] = execute(calls[i].target, calls[i].value, calls[i].data, operation);
}
}

/// @dev Executes a transaction from the IP Account.
Expand All @@ -202,4 +204,36 @@ contract IPAccountImpl is IPAccountStorage, IIPAccount {
}
}
}

/// @dev Updates the IP Account's state all execute transactions.
/// @param to The "target" of the execute transactions.
/// @param value The amount of Ether to send.
/// @param data The data to send along with the transaction.
function _updateStateForExecute(address to, uint256 value, bytes calldata data) internal {
bytes32 newState = keccak256(
abi.encode(state(), abi.encodeWithSignature("execute(address,uint256,bytes)", to, value, data))
);
assembly {
sstore(_ERC6551_STATE_SLOT, newState)
}
}

/// @dev Override Solady 6551 _isValidSigner function.
/// @param signer The signer to check
/// @param extraData The extra data to check against, it should bethe address of the recipient for IPAccount
/// @param context The context for validating the signer
/// @return bool is true if the signer is valid, false otherwise
function _isValidSigner(
address signer,
bytes32 extraData,
bytes calldata context
) internal view override returns (bool) {
return _isValidSigner(signer, address(uint160(uint256(extraData))), context);
}

/// @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";
version = "1";
}
}
4 changes: 2 additions & 2 deletions contracts/IPAccountStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity ^0.8.23;
import { IIPAccountStorage } from "./interfaces/IIPAccountStorage.sol";
import { IModuleRegistry } from "./interfaces/registries/IModuleRegistry.sol";
import { Errors } from "./lib/Errors.sol";
import { ERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
import { ERC165, IERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
import { ShortString, ShortStrings } from "@openzeppelin/contracts/utils/ShortStrings.sol";
/// @title IPAccount Storage
/// @dev Implements the IIPAccountStorage interface for managing IPAccount's state using a namespaced storage pattern.
Expand Down Expand Up @@ -66,7 +66,7 @@ contract IPAccountStorage is ERC165, IIPAccountStorage {
}

/// @notice ERC165 interface identifier for IIPAccountStorage
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165) returns (bool) {
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
return interfaceId == type(IIPAccountStorage).interfaceId || super.supportsInterface(interfaceId);
}

Expand Down
13 changes: 6 additions & 7 deletions contracts/interfaces/IIPAccount.sol
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;

import { IERC721Receiver } from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import { IERC1155Receiver } from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol";
import { IERC6551Account } from "erc6551/interfaces/IERC6551Account.sol";
import { IIPAccountStorage } from "./IIPAccountStorage.sol";

/// @title IIPAccount
Expand All @@ -14,13 +11,13 @@ import { IIPAccountStorage } from "./IIPAccountStorage.sol";
/// IPAccount can interact with modules by making calls as a normal transaction sender.
/// This allows for seamless operations on the state and data of IP.
/// IPAccount is core identity for all actions.
interface IIPAccount is IERC6551Account, IERC721Receiver, IERC1155Receiver, IIPAccountStorage {
interface IIPAccount is IIPAccountStorage {
/// @notice Emitted when a transaction is executed.
/// @param to The recipient of the transaction.
/// @param value The amount of Ether sent.
/// @param data The data sent along with the transaction.
/// @param nonce The nonce of the transaction.
event Executed(address indexed to, uint256 value, bytes data, uint256 nonce);
event Executed(address indexed to, uint256 value, bytes data, bytes32 nonce);

/// @notice Emitted when a transaction is executed on behalf of the signer.
/// @param to The recipient of the transaction.
Expand All @@ -34,14 +31,16 @@ interface IIPAccount is IERC6551Account, IERC721Receiver, IERC1155Receiver, IIPA
address indexed to,
uint256 value,
bytes data,
uint256 nonce,
bytes32 nonce,
uint256 deadline,
address indexed signer,
bytes signature
);

receive() external payable;

/// @notice Returns the IPAccount's internal nonce for transaction ordering.
function state() external view returns (uint256);
function state() external view returns (bytes32);

/// @notice Returns the identifier of the non-fungible token which owns the account
/// @return chainId The EIP-155 ID of the chain the token exists on
Expand Down
4 changes: 3 additions & 1 deletion contracts/interfaces/IIPAccountStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf
pragma solidity ^0.8.23;

import { IERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol";

/// @title IPAccount Namespaced Storage Interface
/// @dev Provides a structured way to store IPAccount's state using a namespaced storage pattern.
/// This interface facilitates conflict-free data writing by different Modules into the same IPAccount
Expand All @@ -15,7 +17,7 @@ pragma solidity ^0.8.23;
/// - Every Module can read data from any namespace.
/// - Only the owning Module (i.e., the Module whose address is used as the namespace) can write data into
/// its respective namespace.
interface IIPAccountStorage {
interface IIPAccountStorage is IERC165 {
/// @dev Sets a bytes value under a given key within the default namespace, determined by `msg.sender`.
/// @param key The key under which to store the value.
/// @param value The bytes value to be stored.
Expand Down
3 changes: 3 additions & 0 deletions contracts/lib/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ library Errors {
/// @notice Provided calldata is invalid.
error IPAccount__InvalidCalldata();

/// @notice Execute operation type is not supported.
error IPAccount__InvalidOperation();

////////////////////////////////////////////////////////////////////////////
// IP Account Storage //
////////////////////////////////////////////////////////////////////////////
Expand Down
2 changes: 1 addition & 1 deletion contracts/lib/MetaTx.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ library MetaTx {
address to;
uint256 value;
bytes data;
uint256 nonce;
bytes32 nonce;
uint256 deadline;
}

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"@openzeppelin/contracts": "5.0.2",
"@openzeppelin/contracts-upgradeable": "5.0.2",
"@openzeppelin/contracts-upgradeable-v4": "npm:@openzeppelin/contracts-upgradeable@4.9.6",
"erc6551": "^0.3.1"
"erc6551": "^0.3.1",
"solady": "^0.0.192"
}
}
3 changes: 2 additions & 1 deletion remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ ds-test/=node_modules/ds-test/src/
forge-std/=node_modules/forge-std/src/
@openzeppelin/=node_modules/@openzeppelin/
@openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades
@create3-deployer/=lib/create3-deployer
@create3-deployer/=lib/create3-deployer
@solady/=node_modules/solady/
Loading
Loading