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

Commit

Permalink
Introducing Meta-Transaction Support to IPAccount with EIP712 Standar…
Browse files Browse the repository at this point in the history
…d Signatures (#32)
  • Loading branch information
kingster-will authored Jan 26, 2024
1 parent d2b9c4b commit 6de2d8b
Show file tree
Hide file tree
Showing 6 changed files with 759 additions and 13 deletions.
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

0 comments on commit 6de2d8b

Please sign in to comment.