Architecture
Core
HolographGenesis.sol
HolographGenesis
is deployed on all blockchains that run and support Holograph Protocol -- all main components are deployed via HolographGenesis
. The key operation of HolographGenesis
is the deploy function, which ensures the protocol's core contracts are deployed at the same address on every chain. This is achieved with the CREATE2 opcode and unique salt to deterministically calculate the same contract address on any EVM-compatible chain.
/**
* @dev Deploy a contract using the EIP-1014 (create2) opcode for deterministic addresses.
* @param chainId The chain on which to deploy
* @param saltHash A unique salt for contract creation
* @param secret A secret part of the salt
* @param sourceCode The bytecode of the contract to deploy
* @param initCode The initialization code for the contract
*/
function deploy(
uint256 chainId,
bytes12 saltHash,
bytes20 secret,
bytes memory sourceCode,
bytes memory initCode
) external onlyDeployer {
require(chainId == block.chainid, "HOLOGRAPH: incorrect chain id");
bytes32 salt = bytes32(abi.encodePacked(secret, saltHash));
address contractAddress = address(
uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, keccak256(sourceCode)))))
);
require(!_isContract(contractAddress), "HOLOGRAPH: already deployed");
assembly {
contractAddress := create2(0, add(sourceCode, 0x20), mload(sourceCode), salt)
}
require(_isContract(contractAddress), "HOLOGRAPH: deployment failed");
require(
InitializableInterface(contractAddress).init(initCode) == InitializableInterface.init.selector,
"HOLOGRAPH: initialization failed"
);
emit ContractDeployed(contractAddress);
}
Holograph.sol
Holograph
is the primary entry-point for all users and developers. A single, universal address across all blockchains enables developers to interact with the protocol’s features. Holograph
keeps references for all current HolographRegistry
, HolographFactory
, and HolographBridge
implementations. Furthermore, it allows for single interface management of the underlying protocol. Holograph
provides a reference to the name and ID of all supported blockchains. Additionally, it:
- Enables custom smart contract logic that is chain agnostic
- Frees developers from having to query and monitor the blockchain
HolographFactory.sol
HolographFactory
enables developers to submit a signed version of the following elements to deploy a holographic contract on the blockchain:
- primary deployment chain
- token type (ERC20, ERC721, ERC1155, etc.)
- event subscriptions
- custom smart contract bytecode
- custom initialization code
Any additional blockchains that developers want to support can have the same signed data submitted to HolographFactory
, allowing for the creation of an identical holographic contract. The primary job of HolographFactory
is to:
- allow propagation of exact data across all blockchains
- ensure a proper standard is selected and used
- ensure all deployments succeed and work as expected
- ensure that security is enforced and impenetrable
The key function provided by HolographFactory
is deployHolographableContract
. This function can be called via applications to create instances of their token contracts consistently across blockchains. Contracts become holographic once deployed with the same config, data, and address on two or more blockchains.
/**
* @notice Deploy a holographable smart contract
* @dev Using this function allows to deploy smart contracts that have the same address across all EVM chains
* @param config contract deployment configurations
* @param signature that was created by the wallet that created the original payload
* @param signer address of wallet that created the payload
*/
function deployHolographableContract(
DeploymentConfig memory config,
Verification memory signature,
address signer
) public {
address registry;
address holograph;
assembly {
holograph := sload(_holographSlot)
registry := sload(_registrySlot)
}
/**
* @dev the configuration is encoded and hashed along with signer address
*/
bytes32 hash = keccak256(
abi.encodePacked(
config.contractType,
config.chainType,
config.salt,
keccak256(config.byteCode),
keccak256(config.initCode),
signer
)
);
/**
* @dev the hash is validated against signature
* this is to guarantee that the original creator's configuration has not been altered
*/
require(_verifySigner(signature.r, signature.s, signature.v, hash, signer), "HOLOGRAPH: invalid signature");
/**
* @dev check that this contract has not already been deployed on this chain
*/
bytes memory holographerBytecode = type(Holographer).creationCode;
address holographerAddress = address(
uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), hash, keccak256(holographerBytecode)))))
);
require(!_isContract(holographerAddress), "HOLOGRAPH: already deployed");
/**
* @dev convert hash into uint256 which will be used as the salt for create2
*/
uint256 saltInt = uint256(hash);
address sourceContractAddress;
bytes memory sourceByteCode = config.byteCode;
assembly {
/**
* @dev deploy the user created smart contract first
*/
sourceContractAddress := create2(0, add(sourceByteCode, 0x20), mload(sourceByteCode), saltInt)
}
assembly {
/**
* @dev deploy the Holographer contract
*/
holographerAddress := create2(0, add(holographerBytecode, 0x20), mload(holographerBytecode), saltInt)
}
/**
* @dev initialize the Holographer contract
*/
require(
InitializableInterface(holographerAddress).init(
abi.encode(abi.encode(config.chainType, holograph, config.contractType, sourceContractAddress), config.initCode)
) == InitializableInterface.init.selector,
"initialization failed"
);
/**
* @dev update the Holograph Registry with deployed contract address
*/
HolographRegistryInterface(registry).setHolographedHashAddress(hash, holographerAddress);
/**
* @dev emit an event that on-chain indexers can easily read
*/
emit BridgeableContractDeployed(holographerAddress, hash);
}
Moreover, HolographFactory
includes native bridge functions that allow for the ability to deploy holographic contracts cross-chain. Finally, HolographFactory
contains getters for accessing the other core contracts
HolographRegistry.sol
HolographRegistry
is a central onchain location where all Holograph data is stored. HolographRegistry
keeps a record of all currently supported standards. New standards can be introduced and enabled as well. Any properly deployed holographic contracts are also stored as reference. This allows for a definitive way to identify whether a smart contract is secure and properly holographic. Verifying entities will be able to identify a holographic contract to ensure the highest level of security and standards have been followed.
Along with providing the ability to look up all contracts deployed via the protocol, HolographRegistry
contains a number of helpers that provide functionality such as checking for Holograph
events, and getters for the other core contracts in the protocol.
HolographBridge.sol
HolographBridge
contains the code responsible for all the bridge-out and bridge-in logic required to facilitate cross-chain transactions. HolographBridge
is a universal smart contract that functions as the primary entry and exit point for any holographic assets to and from all supported blockchains. HolographBridge
validates and ensures integrity and standards enforcement for every bridge-out and bridge-in request. Additionally, HolographBridge
implements a universal standard for sending tokens across blockchains by abstracting away complexities into sub-modules that remove the burden of management for developers. This allows for simple one-click/one-transaction native gas token payment-based interactions for all bridge requests.
The main hooks that provide the bridge-out and bridge-in functionality are via the bridgeOutRequest
and bridgeInRequest
functions.
/**
* @notice Create a beam request for a destination chain
* @dev This function works for deploying contracts and beaming supported holographable assets across chains
* @param toChain Holograph Chain ID where the beam is being sent to
* @param holographableContract address of the contract for which the bridge request is being made
* @param gasLimit maximum amount of gas to spend for executing the beam on destination chain
* @param gasPrice maximum amount of gas price (in destination chain native gas token) to pay on destination chain
* @param bridgeOutPayload actual abi encoded bytes of the data that the holographable contract bridgeOut function will receive
*/
function bridgeOutRequest(
uint32 toChain,
address holographableContract,
uint256 gasLimit,
uint256 gasPrice,
bytes calldata bridgeOutPayload
) external payable {
/**
* @dev check that the target contract is either Holograph Factory or a deployed holographable contract
*/
require(
_registry().isHolographedContract(holographableContract) || address(_factory()) == holographableContract,
"HOLOGRAPH: not holographed"
);
/**
* @dev make a bridgeOut function call to the holographable contract
*/
(bytes4 selector, bytes memory returnedPayload) = Holographable(holographableContract).bridgeOut(
toChain,
msg.sender,
bridgeOutPayload
);
/**
* @dev ensure returned selector is bridgeOut function signature, to guarantee that the function was called and succeeded
*/
require(selector == Holographable.bridgeOut.selector, "HOLOGRAPH: bridge out failed");
/**
* @dev pass the request, along with all data, to Holograph Operator, to handle the cross-chain messaging logic
*/
_operator().send{value: msg.value}(
gasLimit,
gasPrice,
toChain,
msg.sender,
_jobNonce(),
holographableContract,
returnedPayload
);
}
/**
* @notice Receive a beam from another chain
* @dev This function can only be called by the Holograph Operator module
* @param fromChain Holograph Chain ID where the brigeOutRequest was created
* @param holographableContract address of the destination contract that the bridgeInRequest is targeted for
* @param hToken address of the hToken contract that wrapped the origin chain native gas token
* @param hTokenRecipient address of recipient for the hToken reward
* @param hTokenValue exact amount of hToken reward in wei
* @param doNotRevert boolean used to specify if the call should revert
* @param bridgeInPayload actual abi encoded bytes of the data that the holographable contract bridgeIn function will receive
*/
function bridgeInRequest(
uint256 /* nonce*/,
uint32 fromChain,
address holographableContract,
address hToken,
address hTokenRecipient,
uint256 hTokenValue,
bool doNotRevert,
bytes calldata bridgeInPayload
) external payable onlyOperator {
/**
* @dev check that the target contract is either Holograph Factory or a deployed holographable contract
*/
require(
_registry().isHolographedContract(holographableContract) || address(_factory()) == holographableContract,
"HOLOGRAPH: not holographed"
);
/**
* @dev make a bridgeIn function call to the holographable contract
*/
bytes4 selector = Holographable(holographableContract).bridgeIn(fromChain, bridgeInPayload);
/**
* @dev ensure returned selector is bridgeIn function signature, to guarantee that the function was called and succeeded
*/
require(selector == Holographable.bridgeIn.selector, "HOLOGRAPH: bridge in failed");
/**
* @dev check if a specific reward amount was assigned to this request
*/
if (hTokenValue > 0 && hTokenRecipient != address(0)) {
/**
* @dev mint the specific hToken amount for hToken recipient
* this value is equivalent to amount that is deposited on origin chain's hToken contract
* recipient can beam the asset to origin chain and unwrap for native gas token at any time
*/
require(
HolographERC20Interface(hToken).holographBridgeMint(hTokenRecipient, hTokenValue) ==
HolographERC20Interface.holographBridgeMint.selector,
"HOLOGRAPH: hToken mint failed"
);
}
/**
* @dev allow the call to revert on demand, for example use case, look into the Holograph Operator's jobEstimator function
*/
require(doNotRevert, "HOLOGRAPH: reverted");
}
These requests can be made by holographic contracts by ensuring that those contracts have the bridgeOut
and bridgeIn
functions.
HolographOperator.sol
HolographOperator
's primary function is to interface with the messaging protocols that are utilized by the protocol for all cross-chain messages, and to ensure the authenticity and validity of all requests being submitted. HolographOperator
ensures that only valid bridge requests are sent/received and allowed to be executed inside the protocol. HolographOperator
has a symbiotic relationship with HolographBridge
, relying on it to pass along bridge requests, as well as allowing it to look up messaging fees. HolographOperator
ensures that requests are coming from HolographBridge
before passing it further along to the selected messaging module.
/**
* @notice Send cross chain bridge request message
* @dev This function is restricted to only be callable by Holograph Bridge
* @param gasLimit maximum amount of gas to spend for executing the beam on destination chain
* @param gasPrice maximum amount of gas price (in destination chain native gas token) to pay on destination chain
* @param toChain Holograph Chain ID where the beam is being sent to
* @param nonce incremented number used to ensure job hashes are unique
* @param holographableContract address of the contract for which the bridge request is being made
* @param bridgeOutPayload bytes made up of the bridgeOutRequest payload
*/
function send(
uint256 gasLimit,
uint256 gasPrice,
uint32 toChain,
address msgSender,
uint256 nonce,
address holographableContract,
bytes calldata bridgeOutPayload
) external payable {
require(msg.sender == _bridge(), "HOLOGRAPH: bridge only call");
CrossChainMessageInterface messagingModule = _messagingModule();
uint256 hlgFee = messagingModule.getHlgFee(toChain, gasLimit, gasPrice, bridgeOutPayload);
address hToken = _registry().getHToken(_holograph().getHolographChainId());
require(hlgFee < msg.value, "HOLOGRAPH: not enough value");
payable(hToken).transfer(hlgFee);
bytes memory encodedData = abi.encodeWithSelector(
HolographBridgeInterface.bridgeInRequest.selector,
/**
* @dev job nonce is an incremented value that is assigned to each bridge request to guarantee unique hashes
*/
nonce,
/**
* @dev including the current holograph chain id (origin chain)
*/
_holograph().getHolographChainId(),
/**
* @dev holographable contract have the same address across all chains, so our destination address will be the same
*/
holographableContract,
/**
* @dev get the current chain's hToken for native gas token
*/
hToken,
/**
* @dev recipient will be defined when operator picks up the job
*/
address(0),
/**
* @dev value is set to zero for now
*/
hlgFee,
/**
* @dev specify that function call should not revert
*/
true,
/**
* @dev attach actual holographableContract function call
*/
bridgeOutPayload
);
/**
* @dev add gas variables to the back for later extraction
*/
encodedData = abi.encodePacked(encodedData, gasLimit, gasPrice);
/**
* @dev Send the data to the current Holograph Messaging Module
* This will be changed to dynamically select which messaging module to use based on destination network
*/
messagingModule.send{value: msg.value - hlgFee}(
gasLimit,
gasPrice,
toChain,
msgSender,
msg.value - hlgFee,
encodedData
);
/**
* @dev for easy indexing, an event is emitted with the payload hash for status tracking
*/
emit CrossChainMessageSent(keccak256(encodedData));
}
Messaging Modules
LayerZero is the only cross-chain message relayer currently supported by the protocol. That said, the protocol was designed to be modular, allowing for all message relaying logic to be compartimentalized within specific modules. Holograph can be expanded to add new message modules simply by adding a new contract to the module
directory that supports the specific message and fee logic of that messaging protocol, while still maintaining the higher level data security and consistency that is propagated through the higher contracts in the stack.
LayerZeroModule.sol
LayerZeroModule
is the interface between Holograph and LayerZero. It exposes all the LayerZero specific functionality for sending/receiving messages, fetching pricing for cross-chain fees, in addition to getting and setting gas parameters specific for each supported chain.
Standards Enforcers
Holograph Protocol uses a stack-based approach to composing ERC standards. The structure ensures that contracts that extend the protocol adhere to the specific standards for the type of token the contract issues.
Holographer.sol
Holographer
exists at the core of all holographic contracts, which is applied whenever a holographic contract is deployed. Holographer pieces together all components and routes all inbound function calls to their proper contracts, ensuring security and the enforcement of specified standards. Holographer
is isolated on its own private layer and is essentially hard-coded into the blockchain.
ERC Enforcers
ERC Enforcers ensure standards compliance and operability. HolographERC20
and HolographERC721
are perfect examples of such enforcers. ERC Enforcers store and manage all data within themselves to ensure security, compliance, integrity, and enforcement of all protocols. Communication is established with custom contracts via specific event hooks. The storage/data layer is isolated privately and not directly accessible by custom contracts.
HolographRoyalties.sol
HolographRoyalties
is an onchain royalties contract for non-fungible token types. It supports a universal module that understands and speaks all of the different royalty standards on the blockchain. HolographRoyalties
is built to be extendable and can have new royalty standards implemented as they are created and agreed upon.
HolographInterfaces.sol
HolographInterfaces
is used to store and share standardized data. It acts as an external library contract. Additionally, Interfaces exposes mapping between various protocol chain identifiers between each other, which allows for a consistent way to communicate between protocols directly or cross-chain.
External Components
Holograph Protocol is designed to be modular and extensible so that developers can build contracts based on the standards they desire with minor lift to gain the holographic attributes that make it trivial to move assets from those contracts cross-chain. Some very simple examples of this can be found within SampleERC20.sol
and SampleERC721.sol
.
Custom Contract
Custom contracts are any type of contract that was developed outside of the protocol. This empowers developers to build their projects however they want. The requirements for enabling a custom contract to be holographic are minimal, and allow for even novice-level developers to implement. Any current and future fungible and non-fungible token contract can easily be made holographic.