Contract rules overview

Both contract types (Protocol and Dynamic) derive from the same base classes in our source code (BaseContract in src/contract/contract.h, and DynamicContract in src/contract/dynamiccontract.h), though each type adheres to specific rules that must be followed to ensure they will work as intended.
For both contracts, you must:
  • Inherit from their base class (Protocol Contracts should inherit BaseContract directly, and Dynamic Contracts should inherit DynamicContract) and make sure you're passing the right arguments for them
  • Manage variables within the state and database during the contract's construction (loading) and destruction (saving)
  • Register callbacks for the contract's functions with the proper function signature (if functions are called by RPC, eth_call, or a transaction)
  • Ensure that, when building the contract, its name must match the contract's class name
    • BaseContract and DynamicContract take a contractName string as an argument - for example, if your contract is called "TestContract", your class would be class TestContract : public DynamicContract { ... } and your constructor call would be DynamicContract(interface, "TestContract", ...)
  • Declare view functions (functions that do not change state) as const and return a std::string with the encoded ABI (e.g. std::string getBalance(Address add) const { ... }).
  • Declare callable functions (functions that do change state and are callable by transactions within ContractManager) as non-const and return void (e.g. void transfer(Address to, uint256_t value) { ... }).
For Protocol Contracts specifically, you must:
  • Override the BaseContract's ethCall() functions to parse transaction arguments and commit/revert them if necessary
  • Manage state changes during ethCall() functions (depending on whether the call is committing or not)
For Dynamic Contracts specifically, you must:
  • Override registerContractFunctions() and call it inside the contract's constructor
  • Provide two constructors: one for contract creation within ContractManager, and one for loading the contract from the database
  • Allow the contract creation only through a transaction call to the ContractManager contract
  • Develop functions for handling your new contract creation, and modify ethCall() functions to register and access them
  • Set all contract variables as private and inherit them from one of the many Safe Variable classes available
  • Allow loops using containers such as SafeUnorderedMap, but keep in mind how Safe Containers work
    • e.g. when you access a key from a SafeUnorderedMap, it'll check if it exists and copy only the key, not the entire map or its value - thus when iterating a loop, you can't assume the "temporary" value is the original one
    • It's recommended that you only loop inside view functions to ensure value safety, but you can do it on non-view functions as well, just be careful when doing so
  • Trigger state changes only via transaction calls to the contract's functions
  • Call updateState(true) at the end of the contract's constructor
  • Use this with all SafeVariables

Global Contract Variables

There are a few global functions that can be used by your contract during an ethCall():
Global Function
return type
Returns the contract's address
Returns the contract's owner
Returns the contract's chainId
Returns the contract's name
Returns the transaction's origin
Returns the transaction's caller
Returns the transaction's value
Returns if the call is committing to state
Dynamic Contracts also have access to the following global functions:
Global Function
return type
const getContract(address)
Returns a contract of type T
const T
callContract(address, ABI, callValue)
Calls contract function
Get the current balance of an address
sendTokens(address, value)
Send tokens to an address