Skip to content

chainId-fix-solution2nd #23

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

Merged
merged 4 commits into from
Jun 23, 2025
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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ The UEAFactoryV1 is the central contract responsible for deploying and managing

- `registerNewChain(bytes32 _chainHash, bytes32 _vmHash)`: Register a new chain with its VM type
- `registerUEA(bytes32 _chainHash, bytes32 _vmHash, address _UEA)`: Register a UEA implementation for a VM type
- `deployUEA(UniversalAccount memory _id)`: Deploy a new UEA for an external chain user
- `computeUEA(UniversalAccount memory _id)`: Compute the address of a UEA before deployment
- `getUEAForOrigin(UniversalAccount memory _id)`: Get the UEA address for a given external chain user
- `deployUEA(UniversalAccountId memory _id)`: Deploy a new UEA for an external chain user
- `computeUEA(UniversalAccountId memory _id)`: Compute the address of a UEA before deployment
- `getUEAForOrigin(UniversalAccountId memory _id)`: Get the UEA address for a given external chain user

## UEA Implementations

Expand Down
14 changes: 7 additions & 7 deletions src/Interfaces/IUEA.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;

import {UniversalAccount, UniversalPayload} from "../libraries/Types.sol";
import {UniversalAccountId, UniversalPayload} from "../libraries/Types.sol";

/**
* @title IUEA (Interface for Universal Executor Account)
Expand All @@ -26,7 +26,7 @@ interface IUEA {

/**
* @dev Initializes the UEA with the Universal Account information.
* @param universalAccount The UniversalAccount struct containing:
* @param universalAccount The UniversalAccountId struct containing:
* - chain: The name of the external chain (e.g., "eip155:1", "eip155:900")
* - owner: The owner's address/public key from the external chain
*
Expand All @@ -35,13 +35,13 @@ interface IUEA {
* - For EVM-based UEAs: An Ethereum address (20 bytes)
* - For SVM-based UEAs: A Solana public key (32 bytes)
*/
function initialize(UniversalAccount memory universalAccount) external;
function initialize(UniversalAccountId memory universalAccount) external;

/**
* @dev Returns the Universal Account information for this UEA.
* @return The UniversalAccount struct containing the chain name and owner key.
* @return The UniversalAccountId struct containing the chain name and owner key.
*/
function universalAccount() external view returns (UniversalAccount memory);
function universalAccount() external view returns (UniversalAccountId memory);

/**
* @dev Verifies if a signature is valid for a given message hash.
Expand All @@ -51,11 +51,11 @@ interface IUEA {
*
* @notice Implementation behavior varies by UEA type:
* - For EVM-based UEAs: Uses ECDSA recovery to verify that the signature was created by the
* address stored in the UniversalAccount.owner field. The owner is expected to be an
* address stored in the UniversalAccountId.owner field. The owner is expected to be an
* Ethereum address represented as bytes.
*
* - For SVM-based UEAs: Uses a precompiled contract to verify Ed25519 signatures, where the
* UniversalAccount.owner field contains a Solana public key. The verification is done through
* UniversalAccountId.owner field contains a Solana public key. The verification is done through
* a call to the VERIFIER_PRECOMPILE address.
*/
function verifyPayloadSignature(bytes32 messageHash, bytes memory signature) external view returns (bool);
Expand Down
10 changes: 5 additions & 5 deletions src/Interfaces/IUEAFactory.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;

import {UniversalAccount} from "../libraries/Types.sol";
import {UniversalAccountId} from "../libraries/Types.sol";

/**
* @title IUEAFactory
Expand All @@ -13,7 +13,7 @@ interface IUEAFactory {
event ChainRegistered(bytes32 indexed chainHash, bytes32 vmHash);

/// @notice Emitted when a new UEA is deployed for an external chain owner
event UEADeployed(address indexed UEA, bytes owner, bytes32 chainHash);
event UEADeployed(address indexed UEA, bytes owner, uint256 sourceChainId, bytes32 chainHash);

/// @notice Emitted when a UEA implementation is registered for a specific VM type
event UEARegistered(bytes32 indexed chainHash, address UEA_Logic, bytes32 vmHash);
Expand Down Expand Up @@ -48,7 +48,7 @@ interface IUEAFactory {
* @param _id The Universal Account information containing chain and owner key
* @return The address of the deployed UEA
*/
function deployUEA(UniversalAccount memory _id) external returns (address);
function deployUEA(UniversalAccountId memory _id) external returns (address);

/**
* @dev Returns the UEA implementation address for a given chain
Expand All @@ -71,13 +71,13 @@ interface IUEAFactory {
* @return account The Universal Account information associated with this UEA
* @return isUEA True if the address addr is a UEA contract. Else it is a native EOA of PUSH chain (i.e., isUEA = false)
*/
function getOriginForUEA(address addr) external view returns (UniversalAccount memory account, bool isUEA);
function getOriginForUEA(address addr) external view returns (UniversalAccountId memory account, bool isUEA);

/**
* @dev Returns the computed UEA address for a given Universal Account ID and deployment status
* @param _id The Universal Account information
* @return uea The address of the UEA (computed deterministically)
* @return isDeployed True if the UEA has already been deployed
*/
function getUEAForOrigin(UniversalAccount memory _id) external view returns (address uea, bool isDeployed);
function getUEAForOrigin(UniversalAccountId memory _id) external view returns (address uea, bool isDeployed);
}
16 changes: 5 additions & 11 deletions src/UEA/UEA_EVM.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {IUEA} from "../Interfaces/IUEA.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {
UniversalAccount,
UniversalAccountId,
UniversalPayload,
DOMAIN_SEPARATOR_TYPEHASH,
UNIVERSAL_PAYLOAD_TYPEHASH
Expand All @@ -24,7 +24,7 @@ contract UEA_EVM is ReentrancyGuard, IUEA {
using ECDSA for bytes32;

// @notice The Universal Account information
UniversalAccount internal id;
UniversalAccountId internal id;
// @notice Flag to track initialization status
bool private initialized;
// @notice The nonce for the UEA
Expand All @@ -35,7 +35,7 @@ contract UEA_EVM is ReentrancyGuard, IUEA {
/**
* @inheritdoc IUEA
*/
function initialize(UniversalAccount memory _id) external {
function initialize(UniversalAccountId memory _id) external {
if (initialized) {
revert Errors.AlreadyInitialized();
}
Expand All @@ -49,21 +49,15 @@ contract UEA_EVM is ReentrancyGuard, IUEA {
* @return bytes32 The domain separator.
*/
function domainSeparator() public view returns (bytes32) {
uint256 chainId;
/* solhint-disable no-inline-assembly */
/// @solidity memory-safe-assembly
assembly {
chainId := chainid()
}
/* solhint-enable no-inline-assembly */
uint256 chainId = id.chainId;

return keccak256(abi.encode(DOMAIN_SEPARATOR_TYPEHASH, keccak256(bytes(VERSION)), chainId, address(this)));
}

/**
* @inheritdoc IUEA
*/
function universalAccount() public view returns (UniversalAccount memory) {
function universalAccount() public view returns (UniversalAccountId memory) {
return id;
}

Expand Down
16 changes: 5 additions & 11 deletions src/UEA/UEA_SVM.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {Errors} from "../libraries/Errors.sol";
import {IUEA} from "../Interfaces/IUEA.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {
UniversalAccount,
UniversalAccountId,
UniversalPayload,
DOMAIN_SEPARATOR_TYPEHASH,
UNIVERSAL_PAYLOAD_TYPEHASH
Expand All @@ -20,7 +20,7 @@ import {

contract UEA_SVM is ReentrancyGuard, IUEA {
// @notice The Universal Account information
UniversalAccount internal id;
UniversalAccountId internal id;
// @notice Flag to track initialization status
bool private initialized;
// @notice The nonce for the UEA
Expand All @@ -35,21 +35,15 @@ contract UEA_SVM is ReentrancyGuard, IUEA {
* @return bytes32 The domain separator.
*/
function domainSeparator() public view returns (bytes32) {
uint256 chainId;
/* solhint-disable no-inline-assembly */
/// @solidity memory-safe-assembly
assembly {
chainId := chainid()
}
/* solhint-enable no-inline-assembly */
uint256 chainId = id.chainId;

return keccak256(abi.encode(DOMAIN_SEPARATOR_TYPEHASH, keccak256(bytes(VERSION)), chainId, address(this)));
}

/**
* @inheritdoc IUEA
*/
function initialize(UniversalAccount memory _id) external {
function initialize(UniversalAccountId memory _id) external {
if (initialized) {
revert Errors.AlreadyInitialized();
}
Expand All @@ -61,7 +55,7 @@ contract UEA_SVM is ReentrancyGuard, IUEA {
/**
* @inheritdoc IUEA
*/
function universalAccount() public view returns (UniversalAccount memory) {
function universalAccount() public view returns (UniversalAccountId memory) {
return id;
}

Expand Down
48 changes: 23 additions & 25 deletions src/UEAFactoryV1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,23 @@ import {Errors} from "./libraries/Errors.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {IUEAFactory} from "./Interfaces/IUEAFactory.sol";
import {UniversalAccount} from "./libraries/Types.sol";
import {UniversalAccountId} from "./libraries/Types.sol";

/**
* @title UEAFactoryV1
* @dev A factory contract for deploying and managing Universal Executor Accounts (UEA) instances.
*
* Key Terms:
* - UEA (Universal Executor Account): Smart contract deployed for external chain users
* to interact with PUSH chain. Each UEA acts as a proxy for its owner.
* - UOA (Universal Owner Address): The address of the external chain owner who
* owns a particular UEA. This key is used for signature verification in UEAs.
* - VM Types: Different virtual machine environments (EVM, SVM, etc.) that require
* specific implementation logic. Each chain is registered with its VM type hash, and
* each VM type hash is mapped to a corresponding UEA implementation contract address.
* This allows the factory to deploy the correct UEA implementation for different
* blockchain environments.
* - Chain identifiers: These follow the CAIP-2 standard (e.g., "eip155:1" for Ethereum mainnet).
* These standardized chain IDs are used to identify which blockchain an account belongs to.
* The full identifier is hashed to produce a chainHash value for internal usage.
* - UEA (Universal Executor Account) : Smart contract deployed for external chain users to interact with PUSH chain.
* Each UEA acts as a proxy for its owner.
* - UOA (Universal Owner Address) : The address of the external chain owner who owns a particular UEA.
* This key is used for signature verification in UEAs.
* - VM Types : Different virtual machine environments (EVM, SVM, etc.) require specific implementation logic.
* Each chain is registered with a VM_TYPE_HASH, and each VM_TYPE_HASH is mapped to a corresponding UEA.
* This allows the factory to deploy the correct UEA implementation for different blockchain environments.
* - Chain identifiers : These follow the CAIP-2 standard (e.g., "eip155:1" for Ethereum mainnet).
* The UniversalAccountId struct uses the chainNamespace and chainId for chain identification.
* The full identifier is hashed to produce a chainHash value for internal usage.
* Note: chainHash = keccak256(abi.encode(_id.chainNamespace, _id.chainId))
*
* The contract uses OZ's Clones library to create deterministic addresses (CREATE2) for UEA instances.
* It keeps track of deployed UEAs and their corresponding user keys from external chains.
Expand All @@ -39,11 +37,11 @@ contract UEAFactoryV1 is Initializable, OwnableUpgradeable, IUEAFactory {
/// @notice Maps VM type hashes to their corresponding UEA implementation addresses
mapping(bytes32 => address) public UEA_VM;

/// @notice Maps UniversalAccount(hash) to their deployed UEA contract addresses
/// @notice Maps UniversalAccountId(hash) to their deployed UEA contract addresses
mapping(bytes32 => address) public UOA_to_UEA;

/// @notice Maps UEA addresses to their Universal Account information
mapping(address => UniversalAccount) private UEA_to_UOA;
mapping(address => UniversalAccountId) private UEA_to_UOA;

/// @notice Maps chain identifiers to their registered VM type hashes
mapping(bytes32 => bytes32) public CHAIN_to_VM;
Expand Down Expand Up @@ -152,14 +150,14 @@ contract UEAFactoryV1 is Initializable, OwnableUpgradeable, IUEAFactory {
* @notice Will revert if the account already exists, the chain is not registered,
* or if no UEA implementation is available for the chain's VM type
*/
function deployUEA(UniversalAccount memory _id) external returns (address) {
function deployUEA(UniversalAccountId memory _id) external returns (address) {
bytes32 salt = generateSalt(_id);
if (UOA_to_UEA[salt] != address(0)) {
revert Errors.AccountAlreadyExists();
}

// Get the appropriate UEA Implementation based on VM type
bytes32 chainHash = keccak256(abi.encode(_id.chain));
bytes32 chainHash = keccak256(abi.encode(_id.chainNamespace, _id.chainId));
(bytes32 vmHash, bool isRegistered) = getVMType(chainHash);
if (!isRegistered) {
revert Errors.InvalidInputArgs();
Expand All @@ -175,7 +173,7 @@ contract UEAFactoryV1 is Initializable, OwnableUpgradeable, IUEAFactory {
UEA_to_UOA[_UEA] = _id; // Store the inverse mapping
IUEA(_UEA).initialize(_id);

emit UEADeployed(_UEA, _id.owner, chainHash);
emit UEADeployed(_UEA, _id.owner, _id.chainId, chainHash);
return _UEA;
}

Expand All @@ -186,8 +184,8 @@ contract UEAFactoryV1 is Initializable, OwnableUpgradeable, IUEAFactory {
* @notice Will revert if the chain is not registered or if no UEA implementation
* is available for the chain's VM type
*/
function computeUEA(UniversalAccount memory _id) public view returns (address) {
bytes32 chainHash = keccak256(abi.encode(_id.chain));
function computeUEA(UniversalAccountId memory _id) public view returns (address) {
bytes32 chainHash = keccak256(abi.encode(_id.chainNamespace, _id.chainId));
(bytes32 vmHash, bool isRegistered) = getVMType(chainHash);
if (!isRegistered) {
revert Errors.InvalidInputArgs();
Expand Down Expand Up @@ -216,7 +214,7 @@ contract UEAFactoryV1 is Initializable, OwnableUpgradeable, IUEAFactory {
}

/// @inheritdoc IUEAFactory
function getOriginForUEA(address addr) external view returns (UniversalAccount memory account, bool isUEA) {
function getOriginForUEA(address addr) external view returns (UniversalAccountId memory account, bool isUEA) {
account = UEA_to_UOA[addr];

// If the address has no associated Universal Account (owner.length == 0),
Expand All @@ -230,8 +228,8 @@ contract UEAFactoryV1 is Initializable, OwnableUpgradeable, IUEAFactory {
}

/// @inheritdoc IUEAFactory
function getUEAForOrigin(UniversalAccount memory _id) external view returns (address uea, bool isDeployed) {
// Generate salt from the UniversalAccount struct
function getUEAForOrigin(UniversalAccountId memory _id) external view returns (address uea, bool isDeployed) {
// Generate salt from the UniversalAccountId struct
bytes32 salt = generateSalt(_id);

// Check if we already have a mapping
Expand All @@ -255,7 +253,7 @@ contract UEAFactoryV1 is Initializable, OwnableUpgradeable, IUEAFactory {
* @param _id The Universal Account information
* @return A unique salt derived from the account information
*/
function generateSalt(UniversalAccount memory _id) public pure returns (bytes32) {
function generateSalt(UniversalAccountId memory _id) public pure returns (bytes32) {
return keccak256(abi.encode(_id));
}
}
5 changes: 3 additions & 2 deletions src/libraries/Types.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
pragma solidity 0.8.26;

// User Struct
struct UniversalAccount {
string chain; // Chain identifier of the owner account (e.g., "eip155:1")
struct UniversalAccountId {
string chainNamespace; // Chain namespace identifier of the owner account (e.g., "eip155" or "solana")
uint256 chainId; // Chain ID of the source chain of the owner of this UEA.
bytes owner; // Owner's public key or address in bytes format
}

Expand Down
Loading