Technical details, specifications for the contracts and cross chain operations between MultiversX mainchain and SovereignChains.
1. Smart Contract on Mainchain
In terms of the user (which can be both an EOA - userWallet - and another smart contract as well) this will be a simple procedure. An address will be able to make a multiTransfer deposit of both fungible/non-fungible/semi-fungible/metaESDT tokens, giving a destination address on the sovereign shard, function to call, arguments and gasLimit if there is a SC execution needed after the cross chain operation.
Endpoint: deposit@address@functionToCall@arguments@gasLimit - it receives a MultiESDTNFTTransfer(this can be an NFT, multiple tokens of multiple types). The contract verifies if address is valid, goes through other modules such as whitelist if exist or fee module and finally, generates a specific logEvent:
Identifier = deposit
- Address = scAddress
- Topics = address, LIST<tokenID, nonceAsBytes, valueAsBytes/ESDTTransferData (value + metadata)>
- Data = localNonce (increasing), functionToCall, Arguments, gasLimit
Now let’s get into a few specifics and details. Each of the modules can be designed as a hook, and call the hook if there is a setup for that hook. This actually makes the contract smaller, although we should reduce the gasLimit for contract preparation. We might make something special for the sovereign shard SCs.
-
Whitelist/Blacklist tokens:
a. Simply create a map of whitelist and blacklist of tokenIDs
b. Check this map on the deposit function
c. This module can be disabled, meaning all tokens can go through -
Fee market: module to customize the fees, might enable it per token:
a. The first transfer from the multiESDTNFTTransfer is taken as payment.
b. If not enabled, there is simply no fee.
c. Add a whitelist of tokens which can be accepted as a fee, and the exact value for the fee for each such token.
d. Make it possible to add value required in some other token. One could set up that all transfers have to be 1$ worth. The contract takes the first transfer, requests safePrice between userFeeToken and baseFeeToken, check whether fee is enough
e. If more FEE is paid than necessary, send it back to the callerAddress.
f. Compute FEE needed based on GasLimit if there is an g. So Fee per transfer and fee per gas has to be added in the fee module.
g. Whitelist a set of addresses which are free of charge, so no fee is taken from them.
h. As the FEE market can be complicated, create it as a separate contract and use a hook to call it. But the FEE contract can receive only the first transfer as ESDTPayment, everything else has to be sent as arguments.
i. All the FEEs are accumulated into a FeeAccumulator Storage
j. Make an endpoint of distributeFees@List<address, percentage>, this can be called by the SovereignHeaderVerifierSC only. -
Whitelist/Blacklist addresses:
a. Simply create a map of whitelist and blacklist of addresses
b. Check this map on the deposit function
c. This module can be disabled, meaning all tokens can go through -
Whitelist/Blacklist actions:
a. Simply create a map of whitelist and blacklist of actions combination of “functionToCall” and address
b. Check this map on the deposit function
c. This module can be disabled, meaning all tokens can go through
d. One might want to set up the SC calls are not allowed, only user to user deposits. -
MaxGaslimit for actions:
a. By default put it to 300 million
b. It can be disabled, or changed -
Check deposit storage for negative or not for TokenID.
a. By default this check is TRUE.
b. Can be set to FALSE only for tokens which have MINT/NFTAddQuantity+NFTRecreate role.
Admin wallet: there is no admin wallet. All the changing of configs has to be done through the SovereignHeaderVerifierSC - which checks the BLS MultiSig and Header integrity from the Sovereign Shards.
However, this would make it hard to make the initial setup. The cross chain operation SC can have an initial setup phase, where the Sovereign Shard Initiator Address (will get into this later on the SovereignFactorySC) will be able to do so, without BLS MultiSig. The cross chain operation SC does not accept deposits until the initial setup is not completed. Adding an Endpoint: finishSetup endpoint, will revoke the admin rights from the SovereignShard Initiator Address and give admin rights to the SovereignHeaderVerifierSC.
TokenTypes: lock and transfer vs burn and mint
On the cross chain operation we can have 2 types of tokens and it depends whether the tokenOwner gave permission to burn and mint/NFTRecreate or not.
By default all tokens are considered to be lock and transfer type. So on deposit, the cross chain operation SC will lock the deposited tokens, and will release those when there is a cross chain operation SC in the other way. On deposit we increase storage for each received token (not the FEE token), and on transfer from the other way around this storage is decreased. This is a must to keep track of how much was received, as we cannot send back more than initially received (although there will be some extra cases). So make this check by default, that deposit storage cannot go into negative values, however this can be disabled depending on token roles, check above.
Endpoint: setToBurnAndMint@list: can be set and modified by initiator address or through SovereignHeaderVerifierSC. Check whether the contract has MINT+BURN/NFTRecreate+AddQuantity+BURN role for all the received tokens. And save the tokenType to storage inside SC.
If a token is set for mint and burn, then on deposit the contract can burn the received tokens, increase the deposit value and finish the processing.
On the Sovereign chain for bridging from MultiversX to Sovereign we do not need a smart contract as the prepared transactions are directly executed as destinationME transactions. (they were executed fully on mainchain - we now need to add the incoming ESDTs to the defined destination accounts).
From the logEvents generated by the ESDTSafe contract, the sovereign chain notifier prepares the Extended Shard Header structure and the Incoming Txs miniblock. All the validators do this. If the leader wants to finalize blocks and include mainchain shard headers it must include all incoming txs from that block. Consensus will verify it.
Endpoint: forwardAndExecute@List<List<TokenID, nonce, value>, address, callerAddress, functionToCall, Arguments, gasLimit>
This endpoint can be called only by the SovereignHeaderVerifierSC, as it represents the bridging back of the tokens from sovereign to mainchain.
First checks: address and callerAddress for every action, this cannot be anything related to Sovereign SCs (cross chain operation, validator, factory, config). It is an extra protection against dubious attacks. Check both addresses against the whitelist/blacklist of addresses if they exist.
Second: For every tokenID we check tokenType, decrease depositStorage - value (in case of deposit check is enabled check for negatives), check whether SC execution is enabled or not and whether there is SC exec in the cross chain operation call or not.
Third: Mint/Create tokens for all those which you have tokenType set to burnAndMint.
In the v1.7next1 protocol upgrade, we will enshrine the sovereign cross chain operation SC more. Meaning a new VM OPCODE will be available for it. Instead of simple asyncCall and treatment after execution, we will simply use ExecuteByCaller with MultiESDTNFTTransfer and all we received as arguments. This means that in case of failure, tokens will be returned to the callerAddress. But only if the callerAddress is a user address, in case callerAddress is a SC we have to go with the aforementioned method. As we use the same cryptographic standards across mainchain and sovereign chains, one user will have the same wallet across all the chains/shards.
The SovereignHeaderVerifierSC will be the parent of the cross chain operation contract, and only the SovereignHeaderVerifierSC is allowed to transfer out funds from the cross chain operation contract.
As they are deployed in same shard, we will use ExecuteOnDestContext between them.
The OutGoingTXData BLS MultiSigned by the validators will be put inside a mainchain transaction and sent by the leader of the Sovereign shard for the current block. This transaction contains a set of token operations for a set of addresses.It will happen in 2 steps:
-
Register the incoming operations hashes:
a. registerCCOps@hashOfHashes@LIST@BLSMultiSig → send txs to multiSig address -
Execute each of the operations by calling:
a. executeCCOp@hashOfHashes@LIST<address<LIST<tokenID, nonceAsBytes, valueAsBytes>@gasLimit@functionToCall@Arguments>> → send txs to esdtSafe
The SovereignHeaderVerifierSC first verifies if the BLSMultiSig is valid and whether it is signed by 67% of the BLSPubkeys registered in the SovereignHeaderVerifierSC. If yes, the contract calls with ExecuteOnDestContext the ESDTSafe contract to transfer the set of tokens.
After cross chain operation execution on main chain, a new log shall be generated, either in case of success/fail with:
- Identifier = executedCCOp
- Topics = hashOfHashes@hashOfCCOp
In case of a failed executedCCOp, the user will receive back its tokens on the destination chain. In this case as the execution is made in the name of the user, if the execution fails, the tokens will be transferred to the users account in the mainchain.
Endpoint: paymentForESDTIdentifiers
On this endpoint anyone can deposit eGLD/wEGLD (which is transformed to eGLD) in order to be later used on registering new SovereingChain ESDTs on the mainchain (needed before the first transfer). It will increase an eGLDDeposit storage on every such deposit.
When a NEW sovereign token ID is catched on the executeCCOps function, the contract will deduce 0.5eGLD from the deposits and accumulate those under another storage - ESDTTokenIDPaid. If there are no funds, the operation will fail and tokens are sent back to the sovereign chain. This way the 0.5eGLD payment needed to register a tokenID will not be played.
Endpoint: claimESDTTokenRegistrationPayments
This can be called by the controller SC only (discussing it in another doc), and will take all accumulated payments from the ESDTTokenIDPaid storage and send them to the controller SC.
2. Cross chain operation Smart Contract on Sovereign
Endpoint: deposit@address@functionToCall@arguments@gasLimit - it receives a MultiESDTNFTTransfer(this can be an NFT, multiple tokens of multiple types). The contract verifies if address is valid, goes through other modules such as whitelist if exist or fee module. The contract emits a logEvent the same way as in mainchain, and the process of creating OutgoingTXs and OutGoingTXData will generate a token operation out of the logEvent.
Identifier = deposit
- Address = scAddress
- Topics = address, LIST<tokenID, nonceAsBytes, valueAsBytes/ESDTTransferData (value + metadata)>
- Data = nonce (increasing), functionToCall, Arguments, gasLimit
We want to have the same sort features like on the mainchain cross chain operation SC, but with a few differences:
- No blacklist of MainChain tokens or tokens which have a different ESDT prefix than the current Sovereign Shard.
- FEE market we want as it is on the other chain
- No whitelist/blacklist of addresses
- Whitelist/Blacklist of actions can happen
- No need for deposit storage checks for tokens coming from mainchain or other sovereigns, thus those with different ESDT prefix than the current Sovereign Shard.
- Initial setup will be done from a config file - directly programmed in the genesis of the sovereign shard. All other changes have to be done through the SovereignHeaderVerifierSC contract, as it is on the mainchain.
- All tokens are with BURN as the transfer from the mainchain to the sovereign does not go through this contract, as it is implemented in the special processing module.
- No registerAndSetAllRoles as there is no need for it.
So in the end we will have roughly the same cross chain operation contract on both MainChain and SovereignChain.