This pull request integrates OrbiterSDK with EVMOne (C++ EVM) VM, marking a significant enhancement in our platform's capability. It allows the execution of Solidity smart contracts alongside native C++ code within the OrbiterSDK environment, enabling more versatile and powerful software solutions.
This pull request is substantial as it introduces extensive modifications across the codebase. One of the key changes involves the ContractManager class, which was previously responsible for managing contracts and their execution. We have now restructured this responsibility, with the State now owning the contracts. The ContractHost, along with the ContractStack, takes charge of managing contract executions, while the ContractManager's role has shifted primarily to contract creation.
Changelog
A concise overview of the key changes implemented in this pull request includes:
Integration of New Libraries: Addition of EVMOne and EVMC libraries. EVMOne serves as the C++ implementation of the Ethereum Virtual Machine, and EVMC provides the necessary C bindings for EVM interactions.
ContractHost Implementation: A new class designed to manage a single instance of external call execution.
ContractStack Implementation: This new class maintains the context utilized by ContractHost to facilitate commits and rollbacks.
Deprecation of EthCallInfo/EthCallInfoAllocated: These have been replaced with evmc_message to streamline interactions.
Shift from ContractManagerInterface to ContractHost: This reflects our move towards more robust and encapsulated management of contract executions.
Revised Role of ContractManager: Now focuses solely on contract creation, with contract ownership transitioning to the State.
ContractFactory: Converted into a namespace, reflecting its specialized function.
State Management: The State now holds ownership of all contracts through the State::accounts_ map.
Type Conversion Functions: Added functions to bridge OrbiterSDK types with EVMC types, enhancing compatibility and integration.
Concepts and Terminology
Before delving into the technical details, it's essential to understand some key concepts and terminologies that underpin the integration of EVM within the OrbiterSDK environment.
What is the State?
The term "State" refers to the continuously updated record of all the data across the entire network at any given point in time. This includes information about all accounts and their balances, contract code, and contract storage. A list of what constitutes the state in OrbiterSDK includes:
Account Balances
EVM Contract Code
EVM Contract Storage
CPP Contracts
Account Nonces
Basically, the entire State::accounts_ map in OrbiterSDK represents the state of the network.
Understanding External Calls vs. Internal Calls and chain of execution
In the context of interacting with smart contracts, calls can be classified into two types: external calls and internal calls.
External Calls: These occur when a contract is invoked from an external source, which can be either a user transaction (e.g. a block and its transactions are being processed through State::processTransaction()) or through a remote procedure call (RPC). External calls serve as entry points into the smart contract from the outside world, initiating a transaction or a query. Within the OrbiterSDK, to process an external call, a ContractHost object is created, which establishes the execution context based on the specified parameters.
Internal Calls: These occur when a smart contract, already running as part of an ongoing transaction/chain of execution, calls another contract or a function within another contract. This is considered an internal process, as it happens within the chain of execution started by an external call. In OrbiterSDK, internal calls utilize the same ContractHost object created for the initial external call. This maintains consistency and control over the execution environment, allowing for functions like commits or rollbacks within the same transactional context.
Basically, a External call creates a new chain of execution, while internal calls only exists within a given chain of execution, internal calls cannot create new chains of execution.
A chain of execution is a sequence of calls that are executed in a given external call, it is always composed with a initial external call and a set of internal calls.
EVM/ContractHost Implementation Details
This section provides detailed insights into the implementation of the ContractHost within the OrbiterSDK integrated with the EVMOne VM.
State Management and VM Instance Creation
The VM is owned and instantiated by the State class, reflecting a crucial design decision to centralize the management of virtual machine resources. This centralization ensures that each execution context is cleanly managed and isolated. Whenever a new transaction or contract call needs to be executed, regardless of its nature (be it an EVM/C++ contract execution or a simple native transfer), the State class is responsible for instantiating a new ContractHost object with the relevant parameters required for execution:
Once an instance of ContractHost is created, it offers methods like execute() to run the contract, simulate() for simulating the transaction (useful for gas estimation), and ethCallView() for making calls to other contracts within a non-state-changing context.
Overridden Functions from evmc::Host
ContractHost extends the functionalities of evmc::Host by overriding several key functions that interface directly with the Ethereum Virtual Machine (EVM), which are obligatory for the VM to interact with the state:
These methods manage everything from account validation to logging, providing access to the state and storage, and handling calls between contracts. The ContractHost class encapsulates these functions, ensuring that each contract execution is isolated and secure.
CPP To Other Contract Calls
The ContractHost class employs templated functions to support flexible and efficient interaction with contracts.
These templates enable passing any combination of arguments and return types (including void) to and from contracts.
This use of templates helps to leverage the fast ABI encoding/decoding processes,
ensuring optimal performance and flexibility during contract execution:
This approach allows for dynamic interaction with contracts without pre-defining all possible function signatures, accommodating various contract behaviors and states dynamically.
EVM To Other Contract Calls
For calls from the EVM to another contract, the ContractHost::call() function plays a crucial role. It is tasked with creating and handling calls to other contracts, encapsulating the complexity of contract interaction within a simple interface:
To execute a contract call within the EVM environment, the evmc_execute function from the EVMC library is utilized. This function orchestrates the execution of contract bytecode, interfacing directly with the Ethereum Virtual Machine:
It's crucial to understand that the VM itself is stateless—it does not maintain any information about the contracts' state or their data. The VM's role is strictly to interpret and execute bytecode according to the Ethereum protocol specifications.
To enable the stateless VM to interact with the state (such as VM storage keys, account balances, or initiating further contract calls), we must provide it with access to the state through the evmc_host_interface and evmc_host_context. The evmc_host_interface contains a set of callback functions that the VM can use to query or modify the state:
Within OrbiterSDK, we use evmc_execute as following:
evmc::Result result (evmc_execute(this->vm_, &this->get_interface(), this->to_context(),
evmc_revision::EVMC_LATEST_STABLE_REVISION, &msg, recipientAcc.code.data(), recipientAcc.code.size()));
The ContractHost is casted into a evmc_host_interface to provide the VM with the necessary state access functions. This allows the VM to interact with the state and execute contract calls within the OrbiterSDK environment.
Managing State Changes: ContractStack in ContractHost
Within the ContractHost, each time a transaction or contract execution alters any state variables—such as creating a new contract, updating a variable, or initiating transfers—it is imperative for the ContractHost to engage the ContractStack. The primary function of the ContractStack is to maintain a record of the original states of these variables. This record-keeping is essential for enabling a complete restoration of the original state in the event of a transaction rollback.
ContractStack Class Overview
The ContractStack class serves a crucial role in safeguarding blockchain integrity by preserving the initial state of variables during modifications that occur throughout contract execution. This capability ensures that any adverse changes can be undone, maintaining the blockchain's consistency and reliability.
Here's an overview of the ContractStack's definition and functionalities:
The existence of only one instance of ContractStack per ContractHost, and its integration within the RAII framework of ContractHost, guarantees that state values are meticulously commited or reverted upon the completion or rollback of transactions.
This design prevents state spill-over between different contract executions, fortifying transaction isolation and integrity across the blockchain network.
This robust mechanism ensures that even in the dynamic and mutable landscape of blockchain transactions, the integrity and consistency of state changes are meticulously maintained, safeguarding against unintended consequences and errors during contract execution.
Facilitating Seamless CPP <-> EVM Integration
Achieving seamless integration between CPP and EVM contracts revolves around the uniformity in the encoding and decoding of arguments. By standardizing these processes, we ensure that calls between different contract types are handled efficiently without the need for separate mechanisms for each.
Determining Contract Types and Executing Calls
The ContractHost plays a critical role in distinguishing whether a contract is implemented in CPP or EVM and executing it accordingly. Below is an example illustrating how CPP contracts can invoke functions in other contracts, whether they are CPP or EVM:
When an EVM contract needs to invoke another contract, the ContractHost::call() function manages the interaction. This overridden function is designed to handle both CPP and EVM contract calls as shown below:
evmc::Result ContractHost::call(const evmc_message& msg) noexcept {
Address recipient(msg.recipient);
auto &recipientAccount = *accounts_[recipient]; // We need to take a reference to the account, not a reference to the pointer.
this->leftoverGas_ = msg.gas;
/// evmc::Result constructor is: _status_code + _gas_left + _output_data + _output_size
if (recipientAccount.contractType == CPP) {
// Uh we are an CPP contract, we need to call the contract evmEthCall function and put the result into a evmc::Result
try {
this->deduceGas(1000); // CPP contract call is 1000 gas
auto& contract = contracts_[recipient];
if (contract == nullptr) {
throw DynamicException("ContractHost call: contract not found");
}
this->setContractVars(contract.get(), Address(msg.sender), Utils::evmcUint256ToUint256(msg.value));
Bytes ret = contract->evmEthCall(msg, this);
return evmc::Result(EVMC_SUCCESS, this->leftoverGas_, 0, ret.data(), ret.size());
} catch (std::exception& e) {
this->evmcThrows_.emplace_back(e.what());
this->evmcThrow_ = true;
return evmc::Result(EVMC_PRECOMPILE_FAILURE, this->leftoverGas_, 0, nullptr, 0);
}
}
evmc::Result result (evmc_execute(this->vm_, &this->get_interface(), this->to_context(),
evmc_revision::EVMC_LATEST_STABLE_REVISION, &msg,
recipientAccount.code.data(), recipientAccount.code.size()));
this->leftoverGas_ = result.gas_left; // gas_left is not linked with leftoverGas_, we need to link it.
this->deduceGas(5000); // EVM contract call is 5000 gas
result.gas_left = this->leftoverGas_; // We need to set the gas left to the leftoverGas_
return result;
}
Streamlining Calls with evmc_message
We have deprecated EthCallInfo and EthCallInfoAllocated in favor of using the evmc_message struct, aligning the call structures between CPP and EVM environments. This uniformity simplifies the interaction framework and reduces the potential for errors and data mismanagement:
Rationale Behind Deprecating ContractManager in Favor of ContractHost/ContractStack
The transition from ContractManager to the ContractHost/ContractStack model was driven by a need for more robust execution safety and context management:
Enhanced Execution Safety: Previously, a single instance of ContractManager handled multiple contracts and their execution contexts, which posed risks like unintended context modifications and erroneous call validations. The new model isolates each contract's execution within a dedicated ContractHost object, employing Resource Acquisition Is Initialization (RAII) principles to manage state changes securely.
Scope and Lifetime Management: ContractHost's lifecycle is confined to a single chain of execution initiated by a external call, preventing context spill-over between calls and ensuring precise state reversions or commits at the end of each call. This is detailed in the ContractHost destructor, which handles the cleanup and state finalization.
RAII Utilization: The ContractHost and ContractStack leverage RAII for effective state management, a significant improvement over the previous model where the context-managing object was not instantiated in an RAII-compliant manner.
This restructuring not only streamlines the execution flow but also significantly enhances the safety and reliability of contract executions within our platform.
See the ContractHost destructor for more information:
TODO: Sorry but I'm still editting the ContractHost class, and the destructor will change lol.
Calling EVM Contracts from C++
To invoke EVM contract functions from C++, we leverage a templated approach that mimics the contract's functions in a C++ class. This method provides a type-safe way to interact with contracts written in Solidity or other EVM-compatible languages.
Implementation Steps:
Define a Proxy C++ Class: Create a C++ class that represents the EVM contract. This class will include stubs of the contract's functions, which do not contain actual logic but serve to match the contract's interface in the blockchain.
class SolMyContract {
public:
uint256_t myFunction(const uint256_t& arg1, const uint256_t& arg2) const {};
static void registerContract() {
ContractReflectionInterface::registerContractMethods<SolMyContract>(
std::vector<std::string>{}, // List of dependencies or related artifacts if any
std::make_tuple("myFunction", &SolMyContract::myFunction, FunctionTypes::View, std::vector<std::string>{"arg1", "arg2"})
);
}
};
Registration: Ensure that the proxy class is registered with the system before any calls are made. Typically, this registration is done once, often in the constructor of the calling C++ contract, to set up the reflection system used for method invocation.
uint256_t AnotherContract::callMyFunction(const Address& targetAddr, const uint256_t& arg1, const uint256_t& arg2) const {
SolMyContract::registerContract(); // Ensure the EVM contract's methods are registered (Can be done only a single time in the constructor)
return this->callContractViewFunction<SolMyContract>(this, targetAddr, &SolMyContract::myFunction, arg1, arg2);
}
Calling C++ Contracts from EVM
Calling a C++ contract from an EVM contract uses a standard Solidity interface to abstract the C++ implementation. This approach ensures that calls from EVM to C++ are as straightforward as EVM-to-EVM calls.
Implementation Steps:
Define a Solidity Interface: Declare an interface in Solidity that matches the signature of the C++ functions you wish to call. This interface acts as a facade, providing a Solidity view of the C++ contract functionalities.
Contract Interaction: Use the defined interface to make calls to the C++ contract. This is handled similarly to any inter-contract communication in Solidity, ensuring a seamless integration layer.
contract AnotherContract {
function callMyFunction(address cppAddr, uint256 arg1, uint256 arg2) public view returns (uint256) {
return MyContract(cppAddr).myFunction(arg1, arg2);
}
}
Future Enhancements:
Contract Deployment: Enable both EVM and CPP contracts to deploy new EVM contracts.
CREATE2 Implementation: Incorporate the CREATE2 opcode for deterministic contract creation.
Performance and Security: Conduct benchmarks and enhance security features, especially for nested calls.
Database Improvements: Optimize the database structure for managing State::accounts_.
Function Safety: Review and improve the noexcept specifications in overridden functions for robust error handling.
Conclusion
This pull request represents a significant milestone in the evolution of our platform, marking the successful integration of CPP and EVM contract functionalities within the OrbiterSDK environment. By standardizing the encoding and decoding mechanisms and streamlining the interaction between different contract types, we have laid a robust foundation for seamless and efficient contract execution.
The introduction of the ContractHost class as the central executor for both CPP and EVM contracts facilitates a uniform handling of contract calls, enhancing our system's scalability and flexibility. The adoption of evmc_message struct across the platform simplifies our architecture, reduces overhead, and minimizes the potential for errors during contract execution.
Key Achievements:
Enhanced Contract Execution: The ContractHost class provides a unified interface for executing both CPP and EVM contracts, ensuring consistent and efficient contract interactions.
Unified Interface: The integration allows for a consistent interface for contract calls, regardless of the underlying technology (CPP or EVM), ensuring that functionality can be extended without compatibility issues.
Efficient State Management: The introduction of the ContractStack class ensures that state changes are reversible, providing robust transaction integrity that is critical for maintaining blockchain consistency.
EVM Integration
This pull request integrates OrbiterSDK with EVMOne (C++ EVM) VM, marking a significant enhancement in our platform's capability. It allows the execution of Solidity smart contracts alongside native C++ code within the OrbiterSDK environment, enabling more versatile and powerful software solutions.
This pull request is substantial as it introduces extensive modifications across the codebase. One of the key changes involves the ContractManager class, which was previously responsible for managing contracts and their execution. We have now restructured this responsibility, with the State now owning the contracts. The ContractHost, along with the ContractStack, takes charge of managing contract executions, while the ContractManager's role has shifted primarily to contract creation.
Changelog
A concise overview of the key changes implemented in this pull request includes:
Concepts and Terminology
Before delving into the technical details, it's essential to understand some key concepts and terminologies that underpin the integration of EVM within the OrbiterSDK environment.
What is the State?
The term "State" refers to the continuously updated record of all the data across the entire network at any given point in time. This includes information about all accounts and their balances, contract code, and contract storage. A list of what constitutes the state in OrbiterSDK includes:
Basically, the entire State::accounts_ map in OrbiterSDK represents the state of the network.
Understanding External Calls vs. Internal Calls and chain of execution
In the context of interacting with smart contracts, calls can be classified into two types: external calls and internal calls.
Basically, a External call creates a new chain of execution, while internal calls only exists within a given chain of execution, internal calls cannot create new chains of execution.
A chain of execution is a sequence of calls that are executed in a given external call, it is always composed with a initial external call and a set of internal calls.
EVM/ContractHost Implementation Details
This section provides detailed insights into the implementation of the ContractHost within the OrbiterSDK integrated with the EVMOne VM.
State Management and VM Instance Creation
The VM is owned and instantiated by the State class, reflecting a crucial design decision to centralize the management of virtual machine resources. This centralization ensures that each execution context is cleanly managed and isolated. Whenever a new transaction or contract call needs to be executed, regardless of its nature (be it an EVM/C++ contract execution or a simple native transfer), the State class is responsible for instantiating a new ContractHost object with the relevant parameters required for execution:
Once an instance of ContractHost is created, it offers methods like execute() to run the contract, simulate() for simulating the transaction (useful for gas estimation), and ethCallView() for making calls to other contracts within a non-state-changing context.
Overridden Functions from evmc::Host
ContractHost extends the functionalities of evmc::Host by overriding several key functions that interface directly with the Ethereum Virtual Machine (EVM), which are obligatory for the VM to interact with the state:
These methods manage everything from account validation to logging, providing access to the state and storage, and handling calls between contracts. The ContractHost class encapsulates these functions, ensuring that each contract execution is isolated and secure.
CPP To Other Contract Calls
The ContractHost class employs templated functions to support flexible and efficient interaction with contracts. These templates enable passing any combination of arguments and return types (including void) to and from contracts. This use of templates helps to leverage the fast ABI encoding/decoding processes, ensuring optimal performance and flexibility during contract execution:
This approach allows for dynamic interaction with contracts without pre-defining all possible function signatures, accommodating various contract behaviors and states dynamically.
EVM To Other Contract Calls
For calls from the EVM to another contract, the ContractHost::call() function plays a crucial role. It is tasked with creating and handling calls to other contracts, encapsulating the complexity of contract interaction within a simple interface:
Executing Contract Calls via EVMC
To execute a contract call within the EVM environment, the evmc_execute function from the EVMC library is utilized. This function orchestrates the execution of contract bytecode, interfacing directly with the Ethereum Virtual Machine:
It's crucial to understand that the VM itself is stateless—it does not maintain any information about the contracts' state or their data. The VM's role is strictly to interpret and execute bytecode according to the Ethereum protocol specifications.
To enable the stateless VM to interact with the state (such as VM storage keys, account balances, or initiating further contract calls), we must provide it with access to the state through the evmc_host_interface and evmc_host_context. The evmc_host_interface contains a set of callback functions that the VM can use to query or modify the state:
Within OrbiterSDK, we use evmc_execute as following:
The ContractHost is casted into a evmc_host_interface to provide the VM with the necessary state access functions. This allows the VM to interact with the state and execute contract calls within the OrbiterSDK environment.
Managing State Changes: ContractStack in ContractHost
Within the ContractHost, each time a transaction or contract execution alters any state variables—such as creating a new contract, updating a variable, or initiating transfers—it is imperative for the ContractHost to engage the ContractStack. The primary function of the ContractStack is to maintain a record of the original states of these variables. This record-keeping is essential for enabling a complete restoration of the original state in the event of a transaction rollback.
ContractStack Class Overview
The ContractStack class serves a crucial role in safeguarding blockchain integrity by preserving the initial state of variables during modifications that occur throughout contract execution. This capability ensures that any adverse changes can be undone, maintaining the blockchain's consistency and reliability.
Here's an overview of the ContractStack's definition and functionalities:
The existence of only one instance of ContractStack per ContractHost, and its integration within the RAII framework of ContractHost, guarantees that state values are meticulously commited or reverted upon the completion or rollback of transactions.
This design prevents state spill-over between different contract executions, fortifying transaction isolation and integrity across the blockchain network.
This robust mechanism ensures that even in the dynamic and mutable landscape of blockchain transactions, the integrity and consistency of state changes are meticulously maintained, safeguarding against unintended consequences and errors during contract execution.
Facilitating Seamless CPP <-> EVM Integration
Achieving seamless integration between CPP and EVM contracts revolves around the uniformity in the encoding and decoding of arguments. By standardizing these processes, we ensure that calls between different contract types are handled efficiently without the need for separate mechanisms for each.
Determining Contract Types and Executing Calls
The ContractHost plays a critical role in distinguishing whether a contract is implemented in CPP or EVM and executing it accordingly. Below is an example illustrating how CPP contracts can invoke functions in other contracts, whether they are CPP or EVM:
EVM -> Another Contract Calls
When an EVM contract needs to invoke another contract, the ContractHost::call() function manages the interaction. This overridden function is designed to handle both CPP and EVM contract calls as shown below:
Streamlining Calls with evmc_message
We have deprecated EthCallInfo and EthCallInfoAllocated in favor of using the evmc_message struct, aligning the call structures between CPP and EVM environments. This uniformity simplifies the interaction framework and reduces the potential for errors and data mismanagement:
Rationale Behind Deprecating ContractManager in Favor of ContractHost/ContractStack
The transition from ContractManager to the ContractHost/ContractStack model was driven by a need for more robust execution safety and context management:
Enhanced Execution Safety: Previously, a single instance of ContractManager handled multiple contracts and their execution contexts, which posed risks like unintended context modifications and erroneous call validations. The new model isolates each contract's execution within a dedicated ContractHost object, employing Resource Acquisition Is Initialization (RAII) principles to manage state changes securely.
Scope and Lifetime Management: ContractHost's lifecycle is confined to a single chain of execution initiated by a external call, preventing context spill-over between calls and ensuring precise state reversions or commits at the end of each call. This is detailed in the ContractHost destructor, which handles the cleanup and state finalization.
RAII Utilization: The ContractHost and ContractStack leverage RAII for effective state management, a significant improvement over the previous model where the context-managing object was not instantiated in an RAII-compliant manner.
This restructuring not only streamlines the execution flow but also significantly enhances the safety and reliability of contract executions within our platform.
See the ContractHost destructor for more information:
Calling EVM Contracts from C++
To invoke EVM contract functions from C++, we leverage a templated approach that mimics the contract's functions in a C++ class. This method provides a type-safe way to interact with contracts written in Solidity or other EVM-compatible languages.
Implementation Steps:
Calling C++ Contracts from EVM
Calling a C++ contract from an EVM contract uses a standard Solidity interface to abstract the C++ implementation. This approach ensures that calls from EVM to C++ are as straightforward as EVM-to-EVM calls.
Implementation Steps:
Future Enhancements:
Conclusion
This pull request represents a significant milestone in the evolution of our platform, marking the successful integration of CPP and EVM contract functionalities within the OrbiterSDK environment. By standardizing the encoding and decoding mechanisms and streamlining the interaction between different contract types, we have laid a robust foundation for seamless and efficient contract execution.
The introduction of the ContractHost class as the central executor for both CPP and EVM contracts facilitates a uniform handling of contract calls, enhancing our system's scalability and flexibility. The adoption of evmc_message struct across the platform simplifies our architecture, reduces overhead, and minimizes the potential for errors during contract execution. Key Achievements: