Skip to content
This repository has been archived by the owner on Apr 30, 2024. It is now read-only.

Introducing Meta-Transaction Support to IPAccount with EIP712 Standard Signatures #32

Merged
merged 5 commits into from
Jan 26, 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
81 changes: 68 additions & 13 deletions contracts/IPAccountImpl.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ import { IERC1155Receiver } from "@openzeppelin/contracts/token/ERC1155/IERC1155
import { IAccessController } from "contracts/interfaces/IAccessController.sol";
import { IERC6551Account } from "lib/reference/src/interfaces/IERC6551Account.sol";
import { IIPAccount } from "contracts/interfaces/IIPAccount.sol";
import { SignatureChecker } from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
import { AccessPermission } from "contracts/lib/AccessPermission.sol";
import { MessageHashUtils } from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
import { MetaTx } from "contracts/lib/MetaTx.sol";
import { Errors } from "contracts/lib/Errors.sol";

/// @title IPAccountImpl
/// @notice The Story Protocol's implementation of the IPAccount.
Expand Down Expand Up @@ -94,24 +99,55 @@ contract IPAccountImpl is IERC165, IIPAccount {
return IAccessController(accessController).checkPermission(address(this), signer_, to_, selector);
}

/// @notice Executes a transaction from the IP Account.
/// @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.
/// @return result The return data from the transaction.
function execute(address to_, uint256 value_, bytes calldata data_) external payable returns (bytes memory result) {
require(_isValidSigner(msg.sender, to_, data_), "Invalid signer");
/// @notice Executes a transaction from the IP Account on behalf of the signer.
/// @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 signer The signer of the transaction.
/// @param deadline The deadline of the transaction signature.
/// @param signature The signature of the transaction, EIP-712 encoded.
function executeWithSig(
address to,
uint256 value,
bytes calldata data,
address signer,
uint256 deadline,
bytes calldata signature
) external payable returns (bytes memory result) {
if (signer == address(0)) {
revert Errors.IPAccount__InvalidSigner();
}

if (deadline < block.timestamp) {
revert Errors.IPAccount__ExpiredSignature();
}

++state;

bool success;
(success, result) = to_.call{ value: value_ }(data_);
bytes32 digest = MessageHashUtils.toTypedDataHash(
MetaTx.calculateDomainSeparator(),
MetaTx.getExecuteStructHash(
MetaTx.Execute({ to: to, value: value, data: data, nonce: state, deadline: deadline })
)
);

if (!success) {
assembly {
revert(add(result, 32), mload(result))
}
if (!SignatureChecker.isValidSignatureNow(signer, digest, signature)) {
revert Errors.IPAccount__InvalidSignature();
}

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

/// @notice Executes a transaction from the IP Account.
/// @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.
/// @return result The return data from the transaction.
function execute(address to, uint256 value, bytes calldata data) external payable returns (bytes memory result) {
++state;
result = _execute(msg.sender, to, value, data);
emit Executed(to, value, data, state);
}

function onERC721Received(address, address, uint256, bytes memory) public pure returns (bytes4) {
Expand All @@ -131,4 +167,23 @@ contract IPAccountImpl is IERC165, IIPAccount {
) public pure returns (bytes4) {
return this.onERC1155BatchReceived.selector;
}

function _execute(
address signer,
address to,
uint256 value,
bytes calldata data
) internal returns (bytes memory result) {
require(_isValidSigner(signer, to, data), "Invalid signer");

bool success;
(success, result) = to.call{ value: value }(data);

if (!success) {
assembly {
revert(add(result, 32), mload(result))
}
}
}

}
30 changes: 30 additions & 0 deletions contracts/interfaces/IIPAccount.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,43 @@ import { IERC6551Account } from "lib/reference/src/interfaces/IERC6551Account.so
/// 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 {
/// @notice Emitted when a transaction is executed.
event Executed(address indexed to, uint256 value, bytes data, uint256 nonce);

/// @notice Emitted when a transaction is executed on behalf of the signer.
event ExecutedWithSig(
address indexed to,
uint256 value,
bytes data,
uint256 nonce,
uint256 deadline,
address indexed signer,
bytes signature
);

/// @notice Executes a transaction from the IP Account.
/// @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.
/// @return The return data from the transaction.
function execute(address to_, uint256 value_, bytes calldata data_) external payable returns (bytes memory);

/// @notice Executes a transaction from the IP Account on behalf of the signer.
/// @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 signer The signer of the transaction.
/// @param deadline The deadline of the transaction signature.
/// @param signature The signature of the transaction, EIP-712 encoded.
function executeWithSig(
address to,
uint256 value,
bytes calldata data,
address signer,
uint256 deadline,
bytes calldata signature
) external payable returns (bytes memory);

/// @notice Returns the owner of the IP Account.
/// @return The address of the owner.
function owner() external view returns (address);
Expand Down
8 changes: 8 additions & 0 deletions contracts/lib/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ pragma solidity ^0.8.19;
/// @title Errors Library
/// @notice Library for all Story Protocol contract errors.
library Errors {
////////////////////////////////////////////////////////////////////////////
// IPAccount //
////////////////////////////////////////////////////////////////////////////
error IPAccount__InvalidSigner();
error IPAccount__InvalidSignature();
error IPAccount__ExpiredSignature();


////////////////////////////////////////////////////////////////////////////
// Module //
////////////////////////////////////////////////////////////////////////////
Expand Down
65 changes: 65 additions & 0 deletions contracts/lib/MetaTx.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// SPDX-License-Identifier: UNLICENSED
// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf
pragma solidity ^0.8.23;

/// @title MetaTx
/// @dev This library provides functions for handling meta transactions in the Story Protocol.
library MetaTx {
/// @dev Version of the EIP712 domain.
string constant EIP712_DOMAIN_VERSION = "1";
/// @dev Hash of the EIP712 domain version.
bytes32 constant EIP712_DOMAIN_VERSION_HASH = keccak256(bytes(EIP712_DOMAIN_VERSION));
/// @dev EIP712 domain type hash.
bytes32 constant EIP712_DOMAIN =
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
/// @dev Execute type hash.
bytes32 constant EXECUTE = keccak256("Execute(address to,uint256 value,bytes data,uint256 nonce,uint256 deadline)");

/// @dev Structure for the Execute type.
struct Execute {
address to;
uint256 value;
bytes data;
uint256 nonce;
uint256 deadline;
}

/// @dev Calculates the EIP712 domain separator for the current contract.
/// @return The EIP712 domain separator.
function calculateDomainSeparator() internal view returns (bytes32) {
return calculateDomainSeparator(address(this));
}

/// @dev Calculates the EIP712 domain separator for a given IP account.
/// @param ipAccount The IP account for which to calculate the domain separator.
/// @return The EIP712 domain separator.
function calculateDomainSeparator(address ipAccount) internal view returns (bytes32) {
return
keccak256(
abi.encode(
EIP712_DOMAIN,
keccak256("Story Protocol IP Account"),
EIP712_DOMAIN_VERSION_HASH,
block.chainid,
ipAccount
)
);
}

/// @dev Calculates the EIP712 struct hash of an Execute.
/// @param execute The Execute to hash.
/// @return The EIP712 struct hash of the Execute.
function getExecuteStructHash(Execute memory execute) internal pure returns (bytes32) {
return
keccak256(
abi.encode(
MetaTx.EXECUTE,
execute.to,
execute.value,
keccak256(execute.data),
execute.nonce,
execute.deadline
)
);
}
}
Loading
Loading