Contract Source Code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IConnection} from "../connection/IConnection.sol";
import {IWalletFactory} from "./IWalletFactory.sol";
import {IWallet} from "./IWallet.sol";
/// @title Wallet Contract
/// @notice A wallet contract that can execute arbitrary contract calls based on verified messages.
/// @dev This contract interacts with a factory and connection for verification and address mapping.
contract Wallet is IWallet {
/// @notice Factory contract for wallet creation and management.
IWalletFactory public immutable factory;
/// @notice Connection contract for cross-chain message verification.
IConnection public immutable connection;
/// @notice The address of the Asset Manager allowed to invoke calls on behalf of a user on token transfers.
address public immutable assetManager;
/// @notice The address of the XToken Manager allowed to invoke calls on behalf of a user on token transfers.
address public immutable xTokenManager;
event CallStored(bytes32 indexed callHash);
mapping(bytes32 => bool) public storedCalls;
/// @notice Initializes the wallet contract.
/// @param _factory The address of the wallet factory contract.
/// @param _connection The address of the connection contract for message verification.
/// @param _assetManager The address of the asset manager contract.
constructor(IWalletFactory _factory, IConnection _connection, address _assetManager, address _xTokenManager) {
require(address(_factory) != address(0), "Invalid factory address");
require(address(_connection) != address(0), "Invalid connection address");
require(_assetManager != address(0), "Invalid asset manager address");
require(_xTokenManager != address(0), "Invalid xToken manager address");
factory = _factory;
connection = _connection;
assetManager = _assetManager;
xTokenManager = _xTokenManager;
}
/// @notice Receives and processes a verified cross-chain message.
/// @param srcChainId The chain ID of the originating chain.
/// @param srcAddress The address of the sender on the originating chain.
/// @param _connSn The unique identifier for the message.
/// @param _payload The encoded payload containing call data.
/// @param signatures An array of signatures for verifying the message.
function recvMessage(
uint256 srcChainId,
bytes calldata srcAddress,
uint256 _connSn,
bytes memory _payload,
bytes[] calldata signatures
) external override {
// Verify the message using the connection contract
connection.verifyMessage(srcChainId, srcAddress, _connSn, _payload, signatures);
// Ensure the caller address matches the expected wallet address from the factory
require(
address(this) == factory.getWallet(srcChainId, srcAddress),
"Mismatched address and caller"
);
// Execute the calls described in the payload
try this.executeCalls(_payload) {} catch (bytes memory) {}
}
/// @notice Allows the asset manager to execute calls on behalf of the wallet.
/// @param data The encoded data containing an array of ContractCall structs.
function assetManagerHook(bytes memory data) external override {
// Restrict access to the asset manager
require(msg.sender == assetManager, "Only AssetManager is allowed");
// Execute the calls described in the data
this.executeCalls(data);
}
/// @notice Allows the xToken manager to execute calls on behalf of the wallet.
/// @param data The encoded data containing an array of ContractCall structs.
function xTokenManagerHook(bytes memory data) external override {
// Restrict access to the asset manager
require(msg.sender == xTokenManager, "Only XTokenManager is allowed");
// Execute the calls described in the data
this.executeCalls(data);
}
/// @notice Executes multiple contract calls described in the input data.
/// @dev Decodes the input data into an array of ContractCall structs and executes them sequentially.
/// @param data The encoded data containing an array of ContractCall structs.
function executeCalls(bytes memory data) external {
require(msg.sender == address(this), "Only this contract can execute calls");
if (data.length == 32) {
bytes32 callHash = bytesToBytes32(data);
storedCalls[callHash] = true;
emit CallStored(callHash);
return;
}
// Decode the input data into an array of ContractCall structs
ContractCall[] memory contractCalls = abi.decode(data, (ContractCall[]));
// Iterate over the array and execute each call
for (uint256 i = 0; i < contractCalls.length; i++) {
executeInner(contractCalls[i].addr, contractCalls[i].value, contractCalls[i].data);
}
}
function executeStored(bytes memory calls) external {
require(storedCalls[keccak256(calls)], "Calls do not match stored calls");
try this.executeCalls(calls) {} catch (bytes memory) {}
delete storedCalls[keccak256(calls)];
}
/// @notice Performs a single arbitrary call to a specified target.
/// @param target The address of the contract or account to call.
/// @param value The amount of Ether to send with the call.
/// @param data The calldata to send with the call.
function executeInner(address target, uint256 value, bytes memory data) internal {
// Perform the call and check the result
(bool success, ) = target.call{value: value}(data);
require(success, "External call failed");
}
/// @notice Simulates recvMessage without signature verification (view function)
/// @dev This function will always revert at the end to prevent actual state changes
/// @param srcChainId The chain ID of the originating chain
/// @param srcAddress The address of the sender on the originating chain
/// @param _payload The encoded payload containing call data
function simulateRecvMessage(
uint256 srcChainId,
bytes calldata srcAddress,
bytes memory _payload
) external {
// Ensure the caller address matches the expected wallet address from the factory
require(
address(this) == factory.getWallet(srcChainId, srcAddress),
"Mismatched address and caller"
);
// Execute the calls described in the payload
this.executeCalls(_payload);
// Always revert to prevent state changes
revert("Simulation completed");
}
function bytesToBytes32(bytes memory source) private pure returns (bytes32 result) {
if (source.length == 0) {
return 0x0;
}
assembly {
result := mload(add(source, 32))
}
}
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.8.0;
interface IConnection {
function sendMessage(
uint256 dstChainId,
bytes memory dstAddress,
bytes memory payload
) external payable;
function verifyMessage(
uint256 srcChainId,
bytes calldata srcAddress,
uint256 connSn,
bytes memory payload,
bytes[] calldata signatures
) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IWalletFactory {
// Public state variables
function implementation() external view returns (address);
function owner() external view returns (address);
// Events
event Deployed(address indexed deployedAddress, bytes32 indexed salt);
// Function signatures
/// @notice Updates the implementation contract address
/// @param _newImplementation The address of the new implementation contract
function updateImplementation(address _newImplementation) external;
/// @notice Transfers ownership to a new address
/// @param _newOwner The address of the new owner
function transferOwnership(address _newOwner) external;
/// @notice Derive the address of a contract deployed with CREATE3
/// @param chainId chainId of address
/// @param user User's address on the specified chain
/// @return computedAddress The derived contract address
function getWallet(uint256 chainId, bytes calldata user) external returns (address computedAddress);
/// @notice Deploy a contract deterministically with CREATE3
/// @param salt Unique salt to differentiate deployments
/// @return deployedAddress Address of the deployed contract
function deploy(bytes32 salt) external returns (address deployedAddress);
/// @notice Derive the address of a contract deployed with CREATE3
/// @param chainId chainId of address
/// @param user User's address on the specified chain
/// @return computedAddress The derived contract address
function getDeployedAddress(uint256 chainId, bytes calldata user) external view returns (address computedAddress);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title IWallet Interface
/// @notice Interface for the Wallet contract.
interface IWallet {
/// @notice Represents a contract call to be executed.
struct ContractCall {
address addr; // Target address of the call
uint256 value; // Ether value to send
bytes data; // Calldata for the call
}
/// @notice Receives and processes a verified cross-chain message.
/// @param srcChainId The chain ID of the originating chain.
/// @param srcAddress The address of the sender on the originating chain.
/// @param _connSn The unique identifier for the message.
/// @param _payload The encoded payload containing call data.
/// @param signatures An array of signatures for verifying the message.
function recvMessage(
uint256 srcChainId,
bytes calldata srcAddress,
uint256 _connSn,
bytes memory _payload,
bytes[] calldata signatures
) external;
/// @notice Allows the asset manager to execute calls on behalf of the wallet.
/// @param data The encoded data containing an array of ContractCall structs.
function assetManagerHook(bytes memory data) external;
/// @notice Allows the asset manager to execute calls on behalf of the wallet.
/// @param data The encoded data containing an array of ContractCall structs.
function xTokenManagerHook(bytes memory data) external;
}