Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/ThyraFactory.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.17;
import {ReentrancyGuard} from "openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol";
import {Create2} from "openzeppelin-contracts/contracts/utils/Create2.sol";
import {SafeProxyFactory, SafeProxy} from "safe-smart-account/contracts/proxies/SafeProxyFactory.sol";
import {SafeHelpers} from "./Libraries/SafeHelpers.sol";
import {ThyraDiamond} from "./ThyraDiamond.sol";
import {IOwnershipFacet} from "./Interfaces/IOwnershipFacet.sol";
/// @title ThyraFactory
/// @author ThyraWallet Team
/// @notice Factory contract for deploying Thyra Accounts (Safe + ThyraDiamond module)
/// @dev All facet implementations should be pre-deployed and shared across all Diamond instances
/// This follows the standard Diamond proxy pattern where facets are reusable implementations
/// @custom:version 1.0.0
contract ThyraFactory is ReentrancyGuard {
/// @notice Version of the factory
string public constant VERSION = "1.0";
/// @notice Safe Proxy Factory address
address public immutable SAFE_PROXY_FACTORY;
/// @notice Safe Singleton implementation address
address public immutable SAFE_SINGLETON;
/// @notice Safe MultiSend contract address
address public immutable SAFE_MULTI_SEND;
/// @notice Safe fallback handler address
address public immutable SAFE_FALLBACK_HANDLER;
/// @notice Pre-deployed facet addresses shared by all Diamond instances
address public immutable DIAMOND_CUT_FACET;
address public immutable DIAMOND_LOUPE_FACET;
address public immutable EXECUTOR_FACET;
address public immutable OWNERSHIP_FACET;
/// @notice ThyraRegistry address for ExecutorFacet instances
address public immutable THYRA_REGISTRY;
/// @notice Track deployment nonces for deterministic addresses
mapping(bytes32 ownersHash => uint256 count) public ownerSafeCount;
/// @notice Events
event ThyraAccountDeployed(
address indexed safeAddress, address indexed diamondAddress, address[] owners, uint256 threshold
);
event ThyraSubAccountDeployed(
address indexed subAccount,
address indexed diamond,
address indexed parentSafe,
address[] owners,
uint256 threshold
);
/// @notice Errors
error SafeProxyCreationFailed();
error DiamondDeploymentFailed();
error ModuleEnableFailed();
error InvalidParentSafe();
/// @notice Constructor - all facet addresses should be pre-deployed implementations
/// @param _safeProxyFactory Address of Safe Proxy Factory
/// @param _safeSingleton Address of Safe Singleton
/// @param _safeMultiSend Address of Safe MultiSend
/// @param _safeFallbackHandler Address of Safe Fallback Handler
/// @param _diamondCutFacet Address of pre-deployed DiamondCutFacet implementation
/// @param _diamondLoupeFacet Address of pre-deployed DiamondLoupeFacet implementation
/// @param _executorFacet Address of pre-deployed ExecutorFacet implementation
/// @param _ownershipFacet Address of pre-deployed OwnershipFacet implementation
/// @param _thyraRegistry Address of ThyraRegistry for ExecutorFacet
constructor(
address _safeProxyFactory,
address _safeSingleton,
address _safeMultiSend,
address _safeFallbackHandler,
address _diamondCutFacet,
address _diamondLoupeFacet,
address _executorFacet,
address _ownershipFacet,
address _thyraRegistry
) {
SAFE_PROXY_FACTORY = _safeProxyFactory;
SAFE_SINGLETON = _safeSingleton;
SAFE_MULTI_SEND = _safeMultiSend;
SAFE_FALLBACK_HANDLER = _safeFallbackHandler;
DIAMOND_CUT_FACET = _diamondCutFacet;
DIAMOND_LOUPE_FACET = _diamondLoupeFacet;
EXECUTOR_FACET = _executorFacet;
OWNERSHIP_FACET = _ownershipFacet;
THYRA_REGISTRY = _thyraRegistry;
}
/// @notice Deploy a new Thyra Account (Safe + ThyraDiamond module)
/// @param _owners List of Safe owners
/// @param _threshold Number of required confirmations
/// @param _salt Salt for deterministic deployment
/// @return _safe Address of deployed Safe wallet
function deployThyraAccount(address[] calldata _owners, uint256 _threshold, bytes32 _salt)
external
nonReentrant
returns (address _safe)
{
bytes32 ownersHash = keccak256(abi.encode(_owners));
// 1. Deploy ThyraDiamond first (will be enabled as module)
address diamond = _deployThyraDiamond(_salt, ownersHash);
// 2. Deploy Safe proxy with inline initialization data
_safe = _createSafe(_setupSafeWithModule(_owners, _threshold, diamond, false, address(0)), _owners, _salt);
// 3. Initialize Diamond with factory and Safe address in one call
IOwnershipFacet(diamond).initialize(address(this), _safe);
// 4. Emit event
emit ThyraAccountDeployed(_safe, diamond, _owners, _threshold);
}
/// @notice Deploy a new Thyra Sub Account with parent Safe as module
/// @param _owners List of Safe owners
/// @param _threshold Number of required confirmations
/// @param _parentSafe Address of parent Safe that will be enabled as module
/// @param _salt Salt for deterministic deployment
/// @return _subAccount Address of deployed Sub Account Safe
function deploySubAccount(address[] calldata _owners, uint256 _threshold, address _parentSafe, bytes32 _salt)
external
nonReentrant
returns (address _subAccount)
{
// Validate parent Safe address
if (_parentSafe == address(0)) revert InvalidParentSafe();
// Note: We don't check if _parentSafe is valid because:
// 1. An invalid _parentSafe brings no benefit to the sub account owner
// 2. It's the caller's responsibility to provide a valid parent Safe
// 3. An invalid module simply won't function, causing no harm
bytes32 ownersHash = keccak256(abi.encode(_owners));
// 1. Deploy ThyraDiamond first (will be enabled as module)
address diamond = _deployThyraDiamond(_salt, ownersHash);
// 2. Deploy Safe proxy with both modules (diamond + parent safe)
_subAccount = _createSafe(_setupSafeWithModule(_owners, _threshold, diamond, true, _parentSafe), _owners, _salt);
// 3. Initialize Diamond with factory and Safe address in one call
IOwnershipFacet(diamond).initialize(address(this), _subAccount);
// 4. Emit event
emit ThyraSubAccountDeployed(_subAccount, diamond, _parentSafe, _owners, _threshold);
}
/// @notice Deploy a new ThyraDiamond contract using pre-deployed facets with nonce retry mechanism
/// @param _salt Salt for deterministic deployment
/// @param _ownersHash Hash of owners array for nonce generation
/// @return diamond Address of deployed ThyraDiamond
function _deployThyraDiamond(bytes32 _salt, bytes32 _ownersHash) private returns (address diamond) {
// Deploy ThyraDiamond with pre-deployed shared facet addresses
bytes memory creationCode = abi.encodePacked(
type(ThyraDiamond).creationCode,
abi.encode(
address(this), // _contractOwner (temporary, will be transferred to Safe)
DIAMOND_CUT_FACET, // _diamondCutFacet (shared implementation)
DIAMOND_LOUPE_FACET, // _diamondLoupeFacet (shared implementation)
EXECUTOR_FACET, // _executorFacet (shared implementation)
OWNERSHIP_FACET // _ownershipFacet (shared implementation)
)
);
// Try deployment with incremental nonce until successful (similar to Safe deployment)
do {
// Generate nonce using the same counter as Safe deployment
uint256 nonce = ownerSafeCount[_ownersHash];
// Generate salt for Create2 deployment with nonce
bytes32 salt = keccak256(abi.encodePacked("ThyraDiamond", _salt, nonce, VERSION));
// Compute the address that would be deployed
address predictedAddress = Create2.computeAddress(salt, keccak256(creationCode), address(this));
// Check if contract already exists at this address
if (predictedAddress.code.length > 0) {
// Address collision, increment nonce and retry
ownerSafeCount[_ownersHash]++;
continue;
}
// Deploy using Create2
diamond = Create2.deploy(0, salt, creationCode);
// Deployment successful, exit loop
break;
} while (ownerSafeCount[_ownersHash] < type(uint256).max);
// Check deployment was successful
if (diamond == address(0)) revert DiamondDeploymentFailed();
}
/// @notice Setup Safe initialization data with modules
/// @param _owners List of Safe owners
/// @param _threshold Number of required confirmations
/// @param _diamond Address of ThyraDiamond to enable as module
/// @param _isSubAccount Whether this is a sub account (needs parent Safe as module)
/// @param _parentSafe Address of parent Safe (only used if isSubAccount is true)
/// @return Encoded setup data for Safe proxy
function _setupSafeWithModule(
address[] memory _owners,
uint256 _threshold,
address _diamond,
bool _isSubAccount,
address _parentSafe
) private view returns (bytes memory) {
// Determine number of transactions needed
uint256 txnCount = _isSubAccount ? 2 : 1;
SafeHelpers.Executable[] memory txns = new SafeHelpers.Executable[](txnCount);
// Always enable ThyraDiamond as module
txns[0] = SafeHelpers.Executable({
callType: SafeHelpers.CallType.CALL,
target: address(0), // Will be set to Safe address during setup
value: 0,
data: abi.encodeWithSignature("enableModule(address)", _diamond)
});
// If this is a sub account, also enable parent Safe as module
if (_isSubAccount) {
if (_parentSafe == address(0)) revert InvalidParentSafe();
txns[1] = SafeHelpers.Executable({
callType: SafeHelpers.CallType.CALL,
target: address(0), // Will be set to Safe address during setup
value: 0,
data: abi.encodeWithSignature("enableModule(address)", _parentSafe)
});
}
// Pack transactions for MultiSend
bytes memory packedTxns = SafeHelpers.packMultisendTxns(txns);
// Return Safe setup call
return abi.encodeWithSignature(
"setup(address[],uint256,address,bytes,address,address,uint256,address)",
_owners, // _owners
_threshold, // _threshold
SAFE_MULTI_SEND, // to (MultiSend for setup)
abi.encodeWithSignature( // data (MultiSend call)
"multiSend(bytes)", packedTxns),
SAFE_FALLBACK_HANDLER, // fallbackHandler
address(0), // paymentToken (ETH)
0, // payment
payable(address(0)) // paymentReceiver
);
}
/// @notice Create Safe proxy with nonce retry mechanism
/// @param _initializer Safe setup data
/// @param _owners List of owners for nonce calculation
/// @param _salt User provided salt
/// @return _safe Address of created Safe
function _createSafe(bytes memory _initializer, address[] calldata _owners, bytes32 _salt)
private
returns (address _safe)
{
bytes32 ownersHash = keccak256(abi.encode(_owners));
// Try deployment with incremental nonce until successful
do {
uint256 nonce = _genNonce(ownersHash, _salt);
try SafeProxyFactory(SAFE_PROXY_FACTORY).createProxyWithNonce(SAFE_SINGLETON, _initializer, nonce) returns (
SafeProxy deployedProxy
) {
_safe = address(deployedProxy);
} catch {
// Nonce collision, will retry with incremented nonce
// ownerSafeCount was already incremented in _genNonce
}
} while (_safe == address(0));
if (_safe == address(0)) revert SafeProxyCreationFailed();
}
/// @notice Generate deterministic nonce for Safe deployment
/// @param _ownersHash Hash of owners array
/// @param _salt User provided salt
/// @return Generated nonce
function _genNonce(bytes32 _ownersHash, bytes32 _salt) private returns (uint256) {
return uint256(keccak256(abi.encodePacked(_ownersHash, ownerSafeCount[_ownersHash]++, _salt, VERSION)));
}
}
"
},
"lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)
pragma solidity ^0.8.20;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
* consider using {ReentrancyGuardTransient} instead.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
uint256 private _status;
/**
* @dev Unauthorized reentrant call.
*/
error ReentrancyGuardReentrantCall();
constructor() {
_status = NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be NOT_ENTERED
if (_status == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
// Any calls to nonReentrant after this point will fail
_status = ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == ENTERED;
}
}
"
},
"lib/openzeppelin-contracts/contracts/utils/Create2.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Create2.sol)
pragma solidity ^0.8.20;
import {Errors} from "./Errors.sol";
/**
* @dev Helper to make usage of the `CREATE2` EVM opcode easier and safer.
* `CREATE2` can be used to compute in advance the address where a smart
* contract will be deployed, which allows for interesting new mechanisms known
* as 'counterfactual interactions'.
*
* See the https://eips.ethereum.org/EIPS/eip-1014#motivation[EIP] for more
* information.
*/
library Create2 {
/**
* @dev There's no code to deploy.
*/
error Create2EmptyBytecode();
/**
* @dev Deploys a contract using `CREATE2`. The address where the contract
* will be deployed can be known in advance via {computeAddress}.
*
* The bytecode for a contract can be obtained from Solidity with
* `type(contractName).creationCode`.
*
* Requirements:
*
* - `bytecode` must not be empty.
* - `salt` must have not been used for `bytecode` already.
* - the factory must have a balance of at least `amount`.
* - if `amount` is non-zero, `bytecode` must have a `payable` constructor.
*/
function deploy(uint256 amount, bytes32 salt, bytes memory bytecode) internal returns (address addr) {
if (address(this).balance < amount) {
revert Errors.InsufficientBalance(address(this).balance, amount);
}
if (bytecode.length == 0) {
revert Create2EmptyBytecode();
}
assembly ("memory-safe") {
addr := create2(amount, add(bytecode, 0x20), mload(bytecode), salt)
// if no address was created, and returndata is not empty, bubble revert
if and(iszero(addr), not(iszero(returndatasize()))) {
let p := mload(0x40)
returndatacopy(p, 0, returndatasize())
revert(p, returndatasize())
}
}
if (addr == address(0)) {
revert Errors.FailedDeployment();
}
}
/**
* @dev Returns the address where a contract will be stored if deployed via {deploy}. Any change in the
* `bytecodeHash` or `salt` will result in a new destination address.
*/
function computeAddress(bytes32 salt, bytes32 bytecodeHash) internal view returns (address) {
return computeAddress(salt, bytecodeHash, address(this));
}
/**
* @dev Returns the address where a contract will be stored if deployed via {deploy} from a contract located at
* `deployer`. If `deployer` is this contract's address, returns the same value as {computeAddress}.
*/
function computeAddress(bytes32 salt, bytes32 bytecodeHash, address deployer) internal pure returns (address addr) {
assembly ("memory-safe") {
let ptr := mload(0x40) // Get free memory pointer
// | | ↓ ptr ... ↓ ptr + 0x0B (start) ... ↓ ptr + 0x20 ... ↓ ptr + 0x40 ... |
// |-------------------|---------------------------------------------------------------------------|
// | bytecodeHash | CCCCCCCCCCCCC...CC |
// | salt | BBBBBBBBBBBBB...BB |
// | deployer | 000000...0000AAAAAAAAAAAAAAAAAAA...AA |
// | 0xFF | FF |
// |-------------------|---------------------------------------------------------------------------|
// | memory | 000000...00FFAAAAAAAAAAAAAAAAAAA...AABBBBBBBBBBBBB...BBCCCCCCCCCCCCC...CC |
// | keccak(start, 85) | ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ |
mstore(add(ptr, 0x40), bytecodeHash)
mstore(add(ptr, 0x20), salt)
mstore(ptr, deployer) // Right-aligned with 12 preceding garbage bytes
let start := add(ptr, 0x0b) // The hashed data starts at the final garbage byte which we will set to 0xff
mstore8(start, 0xff)
addr := and(keccak256(start, 85), 0xffffffffffffffffffffffffffffffffffffffff)
}
}
}
"
},
"lib/safe-smart-account/contracts/proxies/SafeProxyFactory.sol": {
"content": "// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.7.0 <0.9.0;
import {SafeProxy} from "./SafeProxy.sol";
/**
* @title Proxy Factory - Allows to create a new proxy contract and execute a message call to the new proxy within one transaction.
* @author Stefan George - @Georgi87
*/
contract SafeProxyFactory {
event ProxyCreation(SafeProxy indexed proxy, address singleton);
event ProxyCreationL2(SafeProxy indexed proxy, address singleton, bytes initializer, uint256 saltNonce);
event ChainSpecificProxyCreationL2(SafeProxy indexed proxy, address singleton, bytes initializer, uint256 saltNonce, uint256 chainId);
/// @dev Allows to retrieve the creation code used for the Proxy deployment. With this it is easily possible to calculate predicted address.
function proxyCreationCode() public pure returns (bytes memory) {
return type(SafeProxy).creationCode;
}
/**
* @notice Internal method to create a new proxy contract using CREATE2. Optionally executes an initializer call to a new proxy.
* @param _singleton Address of singleton contract. Must be deployed at the time of execution.
* @param initializer (Optional) Payload for a message call to be sent to a new proxy contract.
* @param salt Create2 salt to use for calculating the address of the new proxy contract.
* @return proxy Address of the new proxy contract.
*/
function deployProxy(address _singleton, bytes memory initializer, bytes32 salt) internal returns (SafeProxy proxy) {
require(isContract(_singleton), "Singleton contract not deployed");
bytes memory deploymentData = abi.encodePacked(type(SafeProxy).creationCode, uint256(uint160(_singleton)));
/* solhint-disable no-inline-assembly */
/// @solidity memory-safe-assembly
assembly {
proxy := create2(0x0, add(0x20, deploymentData), mload(deploymentData), salt)
}
/* solhint-enable no-inline-assembly */
require(address(proxy) != address(0), "Create2 call failed");
if (initializer.length > 0) {
/* solhint-disable no-inline-assembly */
/// @solidity memory-safe-assembly
assembly {
if iszero(call(gas(), proxy, 0, add(initializer, 0x20), mload(initializer), 0, 0)) {
let ptr := mload(0x40)
returndatacopy(ptr, 0x00, returndatasize())
revert(ptr, returndatasize())
}
}
/* solhint-enable no-inline-assembly */
}
}
/**
* @notice Deploys a new proxy with `_singleton` singleton and `saltNonce` salt. Optionally executes an initializer call to a new proxy.
* @param _singleton Address of singleton contract. Must be deployed at the time of execution.
* @param initializer Payload for a message call to be sent to a new proxy contract.
* @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract.
*/
function createProxyWithNonce(address _singleton, bytes memory initializer, uint256 saltNonce) public returns (SafeProxy proxy) {
// If the initializer changes the proxy address should change too. Hashing the initializer data is cheaper than just concatenating it
bytes32 salt = keccak256(abi.encodePacked(keccak256(initializer), saltNonce));
proxy = deployProxy(_singleton, initializer, salt);
emit ProxyCreation(proxy, _singleton);
}
/**
* @notice Deploys a new proxy with `_singleton` singleton and `saltNonce` salt. Optionally executes an initializer call to a new proxy.
* @dev Emits an extra event to allow tracking of `initializer` and `saltNonce`.
* @param _singleton Address of singleton contract. Must be deployed at the time of execution.
* @param initializer Payload for a message call to be sent to a new proxy contract.
* @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract.
*/
function createProxyWithNonceL2(address _singleton, bytes memory initializer, uint256 saltNonce) public returns (SafeProxy proxy) {
proxy = createProxyWithNonce(_singleton, initializer, saltNonce);
emit ProxyCreationL2(proxy, _singleton, initializer, saltNonce);
}
/**
* @notice Deploys a new chain-specific proxy with `_singleton` singleton and `saltNonce` salt. Optionally executes an initializer call to a new proxy.
* @dev Allows to create a new proxy contract that should exist only on 1 network (e.g. specific governance or admin accounts)
* by including the chain id in the create2 salt. Such proxies cannot be created on other networks by replaying the transaction.
* @param _singleton Address of singleton contract. Must be deployed at the time of execution.
* @param initializer Payload for a message call to be sent to a new proxy contract.
* @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract.
*/
function createChainSpecificProxyWithNonce(
address _singleton,
bytes memory initializer,
uint256 saltNonce
) public returns (SafeProxy proxy) {
// If the initializer changes the proxy address should change too. Hashing the initializer data is cheaper than just concatenating it
bytes32 salt = keccak256(abi.encodePacked(keccak256(initializer), saltNonce, getChainId()));
proxy = deployProxy(_singleton, initializer, salt);
emit ProxyCreation(proxy, _singleton);
}
/**
* @notice Deploys a new chain-specific proxy with `_singleton` singleton and `saltNonce` salt. Optionally executes an initializer call to a new proxy.
* @dev Allows to create a new proxy contract that should exist only on 1 network (e.g. specific governance or admin accounts)
* by including the chain id in the create2 salt. Such proxies cannot be created on other networks by replaying the transaction.
* Emits an extra event to allow tracking of `initializer` and `saltNonce`.
* @param _singleton Address of singleton contract. Must be deployed at the time of execution.
* @param initializer Payload for a message call to be sent to a new proxy contract.
* @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract.
*/
function createChainSpecificProxyWithNonceL2(
address _singleton,
bytes memory initializer,
uint256 saltNonce
) public returns (SafeProxy proxy) {
proxy = createChainSpecificProxyWithNonce(_singleton, initializer, saltNonce);
emit ChainSpecificProxyCreationL2(proxy, _singleton, initializer, saltNonce, getChainId());
}
/**
* @notice Returns true if `account` is a contract.
* @dev This function will return false if invoked during the constructor of a contract,
* as the code is not created until after the constructor finishes.
* @param account The address being queried
* @return True if `account` is a contract
*/
function isContract(address account) internal view returns (bool) {
uint256 size;
/* solhint-disable no-inline-assembly */
/// @solidity memory-safe-assembly
assembly {
size := extcodesize(account)
}
/* solhint-enable no-inline-assembly */
return size > 0;
}
/**
* @notice Returns the ID of the chain the contract is currently deployed on.
* @return The ID of the current chain as a uint256.
*/
function getChainId() public view returns (uint256) {
uint256 id;
/* solhint-disable no-inline-assembly */
/// @solidity memory-safe-assembly
assembly {
id := chainid()
}
/* solhint-enable no-inline-assembly */
return id;
}
}
"
},
"src/Libraries/SafeHelpers.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.17;
/// @title SafeHelpers - Library for Safe transaction encoding
/// @notice Based on Brahma.fi implementation for enhanced security
library SafeHelpers {
enum CallType {
CALL, // 0 - Regular call
DELEGATECALL // 1 - Delegate call
}
struct Executable {
CallType callType;
address target;
uint256 value;
bytes data;
}
/// @notice Errors
error InvalidMultiSendInput();
error UnableToParseOperation();
/// @notice Pack multiple transactions for MultiSend
/// @param _txns Array of executable transactions
/// @return packedTxns Packed transaction data
/// @dev Enhanced version based on Brahma implementation with strict validation
function packMultisendTxns(Executable[] memory _txns) internal pure returns (bytes memory packedTxns) {
uint256 len = _txns.length;
if (len == 0) revert InvalidMultiSendInput();
uint256 i = 0;
do {
uint8 call = uint8(_parseOperationEnum(_txns[i].callType));
uint256 calldataLength = _txns[i].data.length;
bytes memory encodedTxn = abi.encodePacked(
bytes1(call), bytes20(_txns[i].target), bytes32(_txns[i].value), bytes32(calldataLength), _txns[i].data
);
if (i != 0) {
// If not first transaction, append to packedTxns
packedTxns = abi.encodePacked(packedTxns, encodedTxn);
} else {
// If first transaction, set packedTxns to encodedTxn
packedTxns = encodedTxn;
}
unchecked {
++i;
}
} while (i < len);
}
/// @notice Converts a CallType enum to operation code with validation
/// @dev Reverts with UnableToParseOperation error if the CallType is not supported
/// @param callType The CallType enum to be converted
/// @return operation The converted operation code (0 for CALL, 1 for DELEGATECALL)
function _parseOperationEnum(CallType callType) internal pure returns (CallType operation) {
if (callType == CallType.DELEGATECALL) {
operation = CallType.DELEGATECALL; // = 1
} else if (callType == CallType.CALL) {
operation = CallType.CALL; // = 0
} else {
revert UnableToParseOperation();
}
}
}
"
},
"src/ThyraDiamond.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.17;
import {LibDiamond} from "./Libraries/LibDiamond.sol";
import {LibDefaultFacets} from "./Libraries/LibDefaultFacets.sol";
// solhint-disable-next-line no-unused-import
import {LibUtil} from "./Libraries/LibUtil.sol";
/// @title Thyra Diamond
/// @author ThyraWallet Team
/// @notice Base EIP-2535 Diamond Proxy Contract for Thyra Account.
/// @custom:version 1.0.0
contract ThyraDiamond {
/// @notice Default facet addresses using immutable for gas-optimized fast path
address private immutable I_DIAMOND_CUT_FACET;
address private immutable I_DIAMOND_LOUPE_FACET;
address private immutable I_EXECUTOR_FACET;
address private immutable I_OWNERSHIP_FACET;
constructor(
address _contractOwner,
address _diamondCutFacet,
address _diamondLoupeFacet,
address _executorFacet,
address _ownershipFacet
) payable {
LibDiamond.setContractOwner(_contractOwner);
// Initialize immutable default facet addresses for fast path optimization
I_DIAMOND_CUT_FACET = _diamondCutFacet;
I_DIAMOND_LOUPE_FACET = _diamondLoupeFacet;
I_EXECUTOR_FACET = _executorFacet;
I_OWNERSHIP_FACET = _ownershipFacet;
// Note: Factory and Safe wallet initialization moved to OwnershipFacet.initialize()
// called by Factory after deployment to avoid constructor call issues
}
// Two-tiered lookup system: fast path + slow path fallback
// Provides gas-optimized fast path lookup for commonly used default facets
// solhint-disable-next-line no-complex-fallback
fallback() external payable {
address facet;
// Phase 1: Fast path - check if default facet (no storage access, extremely low gas cost)
LibDefaultFacets.DefaultFacetType facetType = LibDefaultFacets.getDefaultFacetType(msg.sig);
if (facetType == LibDefaultFacets.DefaultFacetType.DiamondCut) {
facet = I_DIAMOND_CUT_FACET;
} else if (facetType == LibDefaultFacets.DefaultFacetType.DiamondLoupe) {
facet = I_DIAMOND_LOUPE_FACET;
} else if (facetType == LibDefaultFacets.DefaultFacetType.Executor) {
facet = I_EXECUTOR_FACET;
} else if (facetType == LibDefaultFacets.DefaultFacetType.Ownership) {
facet = I_OWNERSHIP_FACET;
} else {
// Phase 2: Slow path - fallback to Diamond storage lookup (compatibility guarantee)
LibDiamond.DiamondStorage storage ds;
bytes32 position = LibDiamond.DIAMOND_STORAGE_POSITION;
// get diamond storage
// solhint-disable-next-line no-inline-assembly
assembly {
ds.slot := position
}
// get facet from function selector
facet = ds.selectorToFacetAndPosition[msg.sig].facetAddress;
}
// Validate facet address validity
if (facet == address(0)) {
revert LibDiamond.FunctionDoesNotExist();
}
// Execute delegatecall to target facet (final execution logic same for both paths)
// solhint-disable-next-line no-inline-assembly
assembly {
// copy function selector and any arguments
calldatacopy(0, 0, calldatasize())
// execute function call using the facet
let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0)
// get any return value
returndatacopy(0, 0, returndatasize())
// return any return value or error back to the caller
switch result
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}
// Able to receive ether
// solhint-disable-next-line no-empty-blocks
receive() external payable {}
}
"
},
"src/Interfaces/IOwnershipFacet.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.17;
import {IERC173} from "./IERC173.sol";
/// @title Interface for OwnershipFacet (Extended)
/// @author ThyraWallet Team
/// @notice Extended ownership interface including Safe wallet initialization
/// @custom:version 1.0.0
interface IOwnershipFacet is IERC173 {
/// @notice Initialize Diamond with factory and Safe wallet
/// @param _factory Address of the Factory that deployed this Diamond
/// @param _safeWallet Address of the Safe wallet
function initialize(address _factory, address _safeWallet) external;
/// @notice Get the Safe wallet address
/// @return Safe wallet address (zero address if not initialized)
function safeWallet() external view returns (address);
/// @notice Get the factory address
/// @return Factory address that deployed this Diamond
function factory() external view returns (address);
}
"
},
"lib/openzeppelin-contracts/contracts/utils/Errors.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Errors.sol)
pragma solidity ^0.8.20;
/**
* @dev Collection of common custom errors used in multiple contracts
*
* IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library.
* It is recommended to avoid relying on the error API for critical functionality.
*
* _Available since v5.1._
*/
library Errors {
/**
* @dev The ETH balance of the account is not enough to perform the operation.
*/
error InsufficientBalance(uint256 balance, uint256 needed);
/**
* @dev A call to an address target failed. The target may have reverted.
*/
error FailedCall();
/**
* @dev The deployment failed.
*/
error FailedDeployment();
/**
* @dev A necessary precompile is missing.
*/
error MissingPrecompile(address);
}
"
},
"lib/safe-smart-account/contracts/proxies/SafeProxy.sol": {
"content": "// SPDX-License-Identifier: LGPL-3.0-only
/* solhint-disable one-contract-per-file */
pragma solidity >=0.7.0 <0.9.0;
/**
* @title IProxy - Helper interface to access the singleton address of the Proxy on-chain.
* @author Richard Meissner - @rmeissner
*/
interface IProxy {
function masterCopy() external view returns (address);
}
/**
* @title SafeProxy - Generic proxy contract allows to execute all transactions applying the code of a master contract.
* @author Stefan George - <stefan@gnosis.io>
* @author Richard Meissner - <richard@gnosis.io>
*/
contract SafeProxy {
// Singleton always needs to be first declared variable, to ensure that it is at the same location in the contracts to which calls are delegated.
// To reduce deployment costs this variable is internal and needs to be retrieved via `getStorageAt`
address internal singleton;
/**
* @notice Constructor function sets address of singleton contract.
* @param _singleton Singleton address.
*/
constructor(address _singleton) {
require(_singleton != address(0), "Invalid singleton address provided");
singleton = _singleton;
}
/// @dev Fallback function forwards all transactions and returns all received return data.
fallback() external payable {
// Note that this assembly block is **intentionally** not marked as memory-safe. First of all, it isn't memory
// safe to begin with, and turning this into memory-safe assembly would just make it less gas efficient.
// Additionally, we noticed that converting this to memory-safe assembly had no affect on optimizations of other
// contracts (as it always gets compiled alone in its own compilation unit anyway). Because the assembly block
// always halts and never returns control back to Solidity, disrespecting Solidity's memory safety invariants
// is not an issue.
/* solhint-disable no-inline-assembly */
assembly {
let _singleton := sload(0)
// 0xa619486e == uint32(bytes4(keccak256("masterCopy()"))). Only the 4 first bytes of calldata are
// considered to make it 100% Solidity ABI conformant.
if eq(shr(224, calldataload(0)), 0xa619486e) {
// We mask the singleton address when handling the `masterCopy()` call to ensure that it is correctly
// ABI-encoded. We do this by shifting the address left by 96 bits (or 12 bytes) and then storing it in
// memory with a 12 byte offset from where the return data starts. Note that we **intentionally** only
// do this for the `masterCopy()` call, since the EVM `DELEGATECALL` opcode ignores the most-significant
// 12 bytes from the address, so we do not need to make sure the top bytes are cleared when proxying
// calls to the `singleton`. This saves us a tiny amount of gas per proxied call. Additionally, we write
// to the "zero-memory" slot instead of the scratch space, which guarantees that 12 bytes of memory
// preceding the singleton address are zero (which would not be guaranteed for the scratch space) [1].
// This ensures that the data we return has the leading 12 bytes set to zero and conforms to the
// Solidity ABI [2].
//
// [1]: https://docs.soliditylang.org/en/v0.7.6/internals/layout_in_memory.html
// [2]: https://docs.soliditylang.org/en/v0.7.6/abi-spec.html#formal-specification-of-the-encoding
mstore(0x6c, shl(96, _singleton))
return(0x60, 0x20)
}
calldatacopy(0, 0, calldatasize())
let success := delegatecall(gas(), _singleton, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
if iszero(success) {
revert(0, returndatasize())
}
return(0, returndatasize())
}
/* solhint-enable no-inline-assembly */
}
}
"
},
"src/Libraries/LibDiamond.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.17;
import {LibDiamond} from "../Libraries/LibDiamond.sol";
import {LibUtil} from "../Libraries/LibUtil.sol";
import {OnlyContractOwner} from "../Errors/GenericErrors.sol";
/// @title LibDiamond
/// @custom:version 1.0.0
/// @notice This library implements the EIP-2535 Diamond Standard
library LibDiamond {
bytes32 internal constant DIAMOND_STORAGE_POSITION = keccak256("diamond.standard.diamond.storage");
// Diamond specific errors
error IncorrectFacetCutAction();
error NoSelectorsInFace();
error FunctionAlreadyExists();
error FacetAddressIsZero();
error FacetAddressIsNotZero();
error FacetContainsNoCode();
error FunctionDoesNotExist();
error FunctionIsImmutable();
error InitZeroButCalldataNotEmpty();
error CalldataEmptyButInitNotZero();
error InitReverted();
// ----------------
struct FacetAddressAndPosition {
address facetAddress;
uint96 functionSelectorPosition; // position in facetFunctionSelectors.functionSelectors array
}
struct FacetFunctionSelectors {
bytes4[] functionSelectors;
uint256 facetAddressPosition; // position of facetAddress in facetAddresses array
}
struct DiamondStorage {
// maps function selector to the facet address and
// the position of the selector in the facetFunctionSelectors.selectors array
mapping(bytes4 => FacetAddressAndPosition) selectorToFacetAndPosition;
// maps facet addresses to function selectors
mapping(address => FacetFunctionSelectors) facetFunctionSelectors;
// facet addresses
address[] facetAddresses;
// Used to query if a contract implements an interface.
// Used to implement ERC-165.
mapping(bytes4 => bool) supportedInterfaces;
// owner of the contract
address contractOwner;
}
enum FacetCutAction {
Add,
Replace,
Remove
}
// Add=0, Replace=1, Remove=2
struct FacetCut {
address facetAddress;
FacetCutAction action;
bytes4[] functionSelectors;
}
function diamondStorage() internal pure returns (DiamondStorage storage ds) {
bytes32 position = DIAMOND_STORAGE_POSITION;
// solhint-disable-next-line no-inline-assembly
assembly {
ds.slot := position
}
}
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata);
function setContractOwner(address _newOwner) internal {
DiamondStorage storage ds = diamondStorage();
address previousOwner = ds.contractOwner;
ds.contractOwner = _newOwner;
emit OwnershipTransferred(previousOwner, _newOwner);
}
function contractOwner() internal view returns (address contractOwner_) {
contractOwner_ = diamondStorage().contractOwner;
}
function enforceIsContractOwner() internal view {
if (msg.sender != diamondStorage().contractOwner) {
revert OnlyContractOwner();
}
}
// Internal function version of diamondCut
function diamondCut(FacetCut[] memory _diamondCut, address _init, bytes memory _calldata) internal {
for (uint256 facetIndex; facetIndex < _diamondCut.length;) {
LibDiamond.FacetCutAction action = _diamondCut[facetIndex].action;
if (action == LibDiamond.FacetCutAction.Add) {
addFunctions(_diamondCut[facetIndex].facetAddress, _diamondCut[facetIndex].functionSelectors);
} else if (action == LibDiamond.FacetCutAction.Replace) {
replaceFunctions(_diamondCut[facetIndex].facetAddress, _diamondCut[facetIndex].functionSelectors);
} else if (action == LibDiamond.FacetCutAction.Remove) {
removeFunctions(_diamondCut[facetIndex].facetAddress, _diamondCut[facetIndex].functionSelectors);
} else {
revert IncorrectFacetCutAction();
}
unchecked {
++facetIndex;
}
}
emit DiamondCut(_diamondCut, _init, _calldata);
initializeDiamondCut(_init, _calldata);
}
function addFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal {
if (_functionSelectors.length == 0) {
revert NoSelectorsInFace();
}
DiamondStorage storage ds = diamondStorage();
if (LibUtil.isZeroAddress(_facetAddress)) {
revert FacetAddressIsZero();
}
uint96 selectorPosition = uint96(ds.facetFunctionSelectors[_facetAddress].functionSelectors.length);
// add new facet address if it does not exist
if (selectorPosition == 0) {
addFacet(ds, _facetAddress);
}
for (uint256 selectorIndex; selectorIndex < _functionSelectors.length;) {
bytes4 selector = _functionSelectors[selectorIndex];
address oldFacetAddress = ds.selectorToFacetAndPosition[selector].facetAddress;
if (!LibUtil.isZeroAddress(oldFacetAddress)) {
revert FunctionAlreadyExists();
}
addFunction(ds, selector, selectorPosition, _facetAddress);
unchecked {
++selectorPosition;
++selectorIndex;
}
}
}
function replaceFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal {
if (_functionSelectors.length == 0) {
revert NoSelectorsInFace();
}
DiamondStorage storage ds = diamondStorage();
if (LibUtil.isZeroAddress(_facetAddress)) {
revert FacetAddressIsZero();
}
uint96 selectorPosition = uint96(ds.facetFunctionSelectors[_facetAddress].functionSelectors.length);
// add new facet address if it does not exist
if (selectorPosition == 0) {
addFacet(ds, _facetAddress);
}
for (uint256 selectorIndex; selectorIndex < _functionSelectors.length;) {
bytes4 selector = _functionSelectors[selectorIndex];
address oldFacetAddress = ds.selectorToFacetAndPosition[selector].facetAddress;
if (oldFacetAddress == _facetAddress) {
revert FunctionAlreadyExists();
}
removeFunction(ds, oldFacetAddress, selector);
addFunction(ds, selector, selectorPosition, _facetAddress);
unchecked {
++selectorPosition;
++selectorIndex;
}
}
}
function removeFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal {
if (_functionSelectors.length == 0) {
revert NoSelectorsInFace();
}
DiamondStorage storage ds = diamondStorage();
// if function does not exist then do nothing and return
if (!LibUtil.isZeroAddress(_facetAddress)) {
revert FacetAddressIsNotZero();
}
for (uint256 selectorIndex; selectorIndex < _functionSelectors.length;) {
bytes4 selector = _functionSelectors[selectorIndex];
address oldFacetAddress = ds.selectorToFacetAndPosition[selector].facetAddress;
removeFunction(ds, oldFacetAddress, selector);
unchecked {
++selectorIndex;
}
}
}
function addFacet(DiamondStorage storage ds, address _facetAddress) internal {
enforceHasContractCode(_facetAddress);
ds.facetFunctionSelectors[_facetAddress].facetAddressPosition = ds.facetAddresses.length;
ds.facetAddresses.push(_facetAddress);
}
function addFunction(DiamondStorage storage ds, bytes4 _selector, uint96 _selectorPosition, address _facetAddress)
internal
{
ds.selectorToFacetAndPosition[_selector].functionSelectorPosition = _selectorPosition;
ds.facetFunctionSelectors[_facetAddress].functionSelectors.push(_selector);
ds.selectorToFacetAndPosition[_selector].facetAddress = _facetAddress;
}
function removeFunction(DiamondStorage storage ds, address _facetAddress, bytes4 _selector) internal {
if (LibUtil.isZeroAddress(_facetAddress)) {
revert FunctionDoesNotExist();
}
// an immutable function is a function defined directly in a diamond
if (_facetAddress == address(this)) {
revert FunctionIsImmutable();
}
// replace selector with last selector, then delete last selector
uint256 selectorPosition = ds.selectorToFacetAndPosition[_selector].functionSelectorPosition;
uint256 lastSelectorPosition = ds.facetFunctionSelectors[_facetAddress].functionSelectors.length - 1;
// if not the same then replace _selector with lastSelector
if (selectorPosition != lastSelectorPosition) {
bytes4 lastSelector = ds.facetFunctionSelectors[_facetAddress].functionSelectors[lastSelectorPosition];
ds.facetFunctionSelectors[_facetAddress].functionSelectors[selectorPosition] = lastSelector;
ds.selectorToFacetAndPosition[lastSelector].functionSelectorPosition = uint96(selectorPosition);
}
// delete the last selector
ds.facetFunctionSelectors[_facetAddress].functionSelectors.pop();
delete ds.selectorToFacetAndPosition[_selector];
// if no more selectors for facet address then delete the facet address
if (lastSelectorPosition == 0) {
// replace facet address with last facet address and delete last facet address
uint256 lastFacetAddressPosition = ds.facetAddresses.length - 1;
uint256 facetAddressPosition = ds.facetFunctionSelectors[_facetAddress].facetAddressPosition;
if (facetAddressPosition != lastFacetAddressPosition) {
address lastFacetAddress = ds.facetAddresses[lastFacetAddressPosition];
ds.facetAddresses[facetAddressPosition] = lastFacetAddress;
ds.facetFunctionSelectors[lastFacetAddress].facetAddressPosition = facetAddressPosition;
}
ds.facetAddresses.pop();
delete ds
.facetFunctionSelectors[_facetAddress]
.facetAddressPosition;
}
}
function initializeDiamondCut(address _init, bytes memory _calldata) internal {
if (LibUtil.isZeroAddress(_init)) {
if (_calldata.length != 0) {
revert InitZeroButCalldataNotEmpty();
}
} else {
if (_calldata.length == 0) {
revert CalldataEmptyButInitNotZero();
}
if (_init != address(this)) {
enforceHasContractCode(_init);
}
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory error) = _init.delegatecall(_calldata);
if (!success) {
if (error.length > 0) {
// bubble up the error
revert(string(error));
} else {
revert InitReverted();
}
}
}
}
function enforceHasContractCode(address _contract) internal view {
uint256 contractSize;
// solhint-disable-next-line no-inline-assembly
assembly {
contractSize := extcodesize(_contract)
}
if (contractSize == 0) {
revert FacetContainsNoCode();
}
}
}
"
},
"src/Libraries/LibDefaultFacets.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.17;
/// @title LibDefaultFacets
/// @author ThyraWallet Team
/// @notice Pure logic library for classifying default facet function selectors for fast-path lookup optimization
/// @dev This library contains no state, only provides function selector to facet type mapping logic
/// @custom:version 1.0.0
library LibDefaultFacets {
/// @notice Default Facet type enumeration
/// @dev None indicates the selector doesn't belong to any default facet, needs storage lookup
enum DefaultFacetType {
None, // Not a default facet, requires storage lookup
DiamondCut, // DiamondCutFacet - Diamond upgrade functionality
DiamondLoupe, // DiamondLoupeFacet - Diamond information query
Executor, // ExecutorFacet - Task execution functionality
Ownership // OwnershipFacet - Ownership management
}
/// @notice Get the corresponding default facet type based on function selector
/// @dev Uses if/else if chain for efficient selector matching, avoiding loops and complex logic
/// @param _selector Function selector to classify
/// @return Corresponding DefaultFacetType, returns None if no match
function getDefaultFacetType(bytes4 _selector) internal pure returns (DefaultFacetType) {
// ExecutorFacet function selectors (HIGHEST FREQUENCY - user transactions)
if (
_selector == 0x0a95afa8 // executeTransaction((address,uint256,bytes,uint8,uint32,bool,uint32,uint32,uint256,uint256,address))
|| _selector == 0xfd1c0a60 // getThyraRegistry()
) {
return DefaultFacetType.Executor;
}
// DiamondLoupeFacet function selectors (MEDIUM FREQUENCY - tooling queries)
else if (
_selector == 0x7a0ed627 // facets()
|| _selector == 0xcdffacc6 // facetAddress(bytes4)
|| _selector == 0x01ffc9a7 // supportsInterface(bytes4)
|| _selector == 0x52ef6b2c // facetAddresses()
|| _selector == 0xadfca15e // facetFunctionSelectors(address)
) {
return DefaultFacetType.DiamondLoupe;
}
// OwnershipFacet function selectors (LOW FREQUENCY - admin operations)
else if (
_selector == 0x8da5cb5b // owner() - Most common ownership query
|| _selector == 0xf2fde38b // transferOwnership(address)
|| _selector == 0x7200b829 // confirmOwnershipTransfer()
|| _selector == 0x23452b9c // cancelOwnershipTransfer()
|| _selector == 0x485cc955 // initialize(address,address)
|| _selector == 0x88cfce56 // safeWallet()
|| _selector == 0xc45a0155 // factory()
) {
return DefaultFacetType.Ownership;
}
// DiamondCutFacet function selectors (LOWEST FREQUENCY - rare upgrades)
else if (_selector == 0x1f931c1c) {
// diamondCut((address,uint8,bytes4[])[],address,bytes)
return DefaultFacetType.DiamondCut;
}
// If no default facet matches, return None
else {
return DefaultFacetType.None;
}
}
}
"
},
"src/Libraries/LibUtil.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
/// @custom:version 1.0.0
pragma solidity ^0.8.17;
// solhint-disable-next-line no-global-import
import "./LibBytes.sol";
library LibUtil {
using LibBytes for bytes;
function getRevertMsg(bytes memory _res) internal pure returns (string memory) {
// If the _res length is less than 68, then the transaction failed silently (without a revert message)
if (_res.length < 68) return "Transaction reverted silently";
bytes memory revertData = _res.slice(4, _res.length - 4); // Remove the selector which is the first 4 bytes
return abi.decode(revertData, (string)); // All that remains is the revert string
}
/// @notice Determines whether the given address is the zero address
/// @param addr The address to verify
/// @return Boolean indicating if the address is the zero address
function isZeroAddress(address addr) internal pure returns (bool) {
return addr == address(0);
}
function revertWith(bytes memory data) internal pure {
assembly {
let dataSize := mload(data) // Load the size of the data
let dataPtr := add(data, 0x20) // Advance data pointer to the next word
revert(dataPtr, dataSize) // Revert with the given data
}
}
}
"
},
"src/Interfaces/IERC173.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.17;
/// @title Interface for ERC-173 (Contract Ownership Standard)
/// @author LI.FI (https://li.fi)
/// Note: the ERC-165 identifier for this interface is 0x7f5828d0
/// @custom:version 1.0.0
interface IERC173 {
/// @dev This emits when ownership of a contract changes.
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/// @notice Get the address of the owner
/// @return owner_ The address of the owner.
function owner() external view returns (address owner_);
/// @notice Set the address of the new owner of the contract
/// @dev Set _newOwner to address(0) to renounce any ownership.
/// @param _newOwner The address of the new owner of the contract
function transferOwnership(address _newOwner) external;
}
"
},
"src/Errors/GenericErrors.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
/// @custom:version 1.0.2
pragma solidity ^0.8.17;
error AlreadyInitialized();
error CannotAuthoriseSelf();
error CannotBridgeToSameNetwork();
error ContractCallNotAllowed();
error CumulativeSlippageTooHigh(uint256 minAmount, uint256 receivedAmount);
error DiamondIsPaused();
error ETHTransferFailed();
error ExternalCallFailed();
error FunctionDoesNotExist();
error InformationMismatch();
error InsufficientBalance(uint256 required, uint256 balance);
error InvalidAmount();
error InvalidCallData();
error InvalidConfig();
error InvalidContract();
error InvalidDestinationChain();
error InvalidFallbackAddress();
error InvalidNonEVMReceiver();
error InvalidReceiver();
error InvalidSendingToken();
error NativeAssetNotSupported();
error NativeAssetTransferFailed();
error NoSwapDataProvided();
error NoSwapFromZeroBalance();
error NotAContract();
error NotInitialized();
error NoTransferToNullAddress();
error NullAddrIsNotAnERC20Token();
error NullAddrIsNotAValidSpender();
error OnlyContractOwner();
error RecoveryAddressCannotBeZero();
error ReentrancyError();
error TokenNotSupported();
error TransferFromFailed();
error UnAuthorized();
error UnsupportedChainId(uint256 chainId);
error WithdrawFailed();
error ZeroAmount();
"
},
"src/Libraries/LibBytes.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
/// @custom:version 1.0.0
pragma solidity ^0.8.17;
library LibBytes {
// solhint-disable no-inline-assembly
// LibBytes specific errors
error SliceOverflow();
error SliceOutOfBounds();
error AddressOutOfBounds();
bytes16 private constant _SYMBOLS = "0123456789abcdef";
// -------------------------
function slice(bytes memory _bytes, uint256 _start, uint256 _length) internal pure returns (bytes memory) {
if (_length + 31 < _length) revert SliceOverflow();
if (_bytes.length < _start + _length) revert SliceOutOfBounds();
bytes memory tempBytes;
assembly {
switch iszero(_length)
case 0 {
// Get a location of some free memory and store it in tempBytes as
// Solidity does for memory variables.
tempBytes := mload(0x40)
// The first word of the slice result is potentially a partial
// word read from the original array. To read it, we calculate
// the length of that partial word and start copying that many
// bytes into the array. The first word we copy will start with
// data we don't care about, but the last `lengthmod` bytes will
// land at the beginning of the contents of the new array. When
// we're done copying, we overwrite the full first word with
// the actual length of the slice.
let lengthmod := and(_length, 31)
// The multiplication in the next line is necessary
// because when slicing multiples of 32 bytes (lengthmod == 0)
// the following copy loop was copying the origin's length
// and then ending prematurely not copying everything it should.
let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod)))
let end := add(mc, _length)
for {
// The multiplication in the next line has the same exact purpose
// as the one above.
let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start)
} lt(mc, end) {
mc := add(mc, 0x20)
cc := add(cc, 0x20)
} { mstore(mc, mload(cc)) }
mstore(tempBytes, _length)
//update free-memory pointer
//allocating the array padded to 32 bytes like the compiler does now
mstore(0x40, and(add(mc, 31), not(31)))
}
//if we want a zero-length slice let's just return a zero-length array
default {
tempBytes := mload(0x40)
//zero out the 32 bytes slice we are about to return
//we need to do it because Solidity does not garbage collect
mstore(tempBytes, 0)
mstore(0x40, add(tempBytes, 0x20))
}
}
return tempBytes;
}
function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) {
if (_bytes.length < _start + 20) {
revert AddressOutOfBounds();
}
address tempAddress;
assembly {
tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000)
}
return tempAddress;
}
/// Copied from OpenZeppelin's `Strings.sol` utility library.
/// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/8335676b0e99944eef6a742e16dcd9ff6e68e609
/// /contracts/utils/Strings.sol
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
bytes memory buffer = new bytes(2 * length + 2);
Submitted on: 2025-11-05 13:31:55
Comments
Log in to comment.
No comments yet.