Comment on page
Contract rules overview
When creating contracts for Sparq, there are a few rules you must follow to ensure they work as intended. While each contract type has its own rules, some other rules apply to both. This will be explained and demonstrated further in the next few subchapters.
As a general stance, contracts must:
- Inherit from their respective base class, depending on their type (see below) and make sure you're passing the right arguments for their constructors
- Manage variables within the state and database during contract construction (loading) and destruction (saving)
- Register callbacks for contract functions with their proper functors/signatures (if functions are called by an RPC
eth_call
or a transaction) - Ensure that their assigned name and their own class name match
- Both contract constructors take a
contractName
string as an argument - i.e. if your contract is called "TestContract", your constructor's definition would beTestContract(...) : DynamicContract(interface, "TestContract", ...)
- both HAVE to match, otherwise a segfault may happen
- Declare view functions (functions that do not change state) as
const
and return astd::string
with the encoded ABI (e.g.std::string getBalance(Address add) const { return ABI::Encoder({balance}).getRaw(); }
) - Declare callable functions (functions that do change state and are callable by transactions) as non-
const
and returnvoid
(e.g.void transfer(Address to, uint256_t value) { ... }
)
Protocol Contracts specifically must:
- Inherit
BaseContract
fromsrc/contract/contract.h
:

- Override
ethCall()
functions to parse transaction arguments, manage state changes during their processing (depending on whether the call is committing or not), and commit/revert variables when necessary
Dynamic Contracts specifically must:
- Inherit
DynamicContract
fromsrc/contract/dynamiccontract.h
:

- Override
registerContractFunctions()
and call it inside the contract's constructor - Provide two constructors: one for creating the contract from scratch within
ContractManager
, and one for loading the contract from the database - Only allow contract creation through a transaction call to the
ContractManager
contract - Develop functions for handling your contract's creation and logic
- Override
ethCall()
functions to register and properly call those functions - Set all of the contract's internal variables as
private
, inherit them from one of the many Safe Variable classes, and always reference them withthis
to ensure correct semantics- e.g.
string name
anduint256 value
in Solidity should beSafeString name
andSafeUint256_t value
in C++, respectively - referencing them in your definition would bethis->name
,this->value
...
- 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 - We recommended 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 contract functions
- Call
updateState(true)
at the end of the contract's constructor
For both contract types (Protocol and Dynamic), you can use the following global functions during an
ethCall()
:Global Function | Description | return type |
---|---|---|
getContractAddress() | Returns the contract's address | Address |
getContractOwner() | Returns the contract's owner | Address |
getContractChainId() | Returns the contract's chainId | uint64_t |
getContractName() | Returns the contract's name | string |
getOrigin() | Returns the transaction's origin | Address |
getCaller() | Returns the transaction's caller | Address |
getValue() | Returns the transaction's value | uint256_t |
getCommit() | Returns if the call is committing to state | bool |
For Dynamic Contracts specifically, you can also use the following global functions:
Global Function | Description | return type |
---|---|---|
const getContract(address) | Returns a contract of type T | const T |
callContract(address, ABI, callValue) | Calls contract function | void |
getBalance(address) | Get the current balance of an address | uint256_t |
sendTokens(address, value) | Send tokens to an address | void |
Last modified 4mo ago