Using Gnosis SAFE Multisig

You can use Ethereum-based Gnosis SAFE with IMA for SKALE chain administration functions.

Diagram of Gnosis SAFE administration of SKALE Chains
Figure 1. Diagram of Gnosis SAFE administration of SKALE Chains

Preparation to SKALE chain creation

No custom steps are required to create a SKALE chain that Gnosis SAFE controls. Ensure that you know the address of your Gnosis SAFE deployed on Ethereum mainnet; if you don’t have one follow https://help.gnosis-safe.io/en/articles/3876461-create-a-safe to create it.

Once you have deployed a Gnosis SAFE, you can use its address as the SKALE chain owner and create a chain as usual.

SKALE chain owner address can’t change after deployment.

Calling functions on a SKALE chain

You can use the multisigwallet-cli to simplify this process.

Installation

Clone and install multisigwallet-cli.

git clone https://github.com/skalenetwork/multisigwallet-cli.git
yarn install

Setup

Change directory into multisigwallet-cli` folder and create a .env file:

cd multisigwallet-cli
touch .env

Add the following environment variables to the .env file:

ENDPOINT=http://localhost:8545
PRIVATE_KEY_1={private key}
You can set any values. See this issue

Prepare transaction data

Call encodeData command of multisigwallet-cli.

For example, to prepare a call of grantRole function of Etherbase smart contract on SKALE chain example-chain with parameters grantRole(0xe0ba7b49edc651b7ad93b374c67f1e9a0d37370168bbb86b81c569ebfa15f046, 0xd2D2D2D2d2d2d2d2D2d2d2d2D2d2D2d2D2d2D2d2) (where 0xe0ba7b49edc651b7ad93b374c67f1e9a0d37370168bbb86b81c569ebfa15f046 is an id of ETHER_MANAGER_ROLE) execute the following:

npx msig encodeData example-chain Etherbase grantRole 0xe0ba7b49edc651b7ad93b374c67f1e9a0d37370168bbb86b81c569ebfa15f046 0xd2D2D2D2d2d2d2d2D2d2d2d2D2d2D2d2D2d2D2d2

It will return a long hex string.

Submitting transaction to Gnosis Safe

  1. Go to Gnosis Safe UI and press the New transactionContract interaction.

  2. Enable the checkbox Use custom data (hex encoded).

  3. Put the IMA address for message_proxy_mainnet_address to the Contract address field. For Ethereum this is 0x8629703a9903515818C2FeB45a6f6fA5df8Da404

    This address probably won’t change, but it’s better to ensure visiting Releases repo. In this repo, you can find the addresses of IMA on different Ethereum testnets.
  4. Enter 0 in the Value field.

  5. Copy the hex string obtained in the previous step from multisigwallet-cli to the Data field.

  6. Review and submit the transaction.

Execution of the transaction

After signing, execute the transaction. Be patient: wait for IMA to pick up the transaction and execute the call. This process can take up to several minutes.

Deeper explanation

The following section isn’t necessary to control a SKALE chain with Gnosis SAFE via multisigwallet-cli but may help integrate with other products. This section describes how to encode a SKALE chain smart contract call into an Ethereum mainnet transaction for sending through IMA.

Marionette

There is a Marionette smart contract that’s predeployed on SKALE chains at address 0xD2c0DeFACe000000000000000000000000000000. It’s granted all SKALE chain owner’s administration rights and serves as a proxy to forward calls sent via IMA.

Marionette has a function postMessage(bytes32 sourceChain, address sender,bytes calldata encodedCall) that IMA calls. It checks that a sender is a SKALE chain owner and performs a call encoded in encodedCall parameter.

encodedCall is a triplet (address receiver, uint value, bytes calldata data) encoded to bytes as arguments according to Contract ABI Specification (See encodeFunctionCall function of Marionette).

Here:

  • receiver is a target contract

  • value is the amount of sFuel transferred in the transaction

  • data is a call data

In the example above, grantRole of Etherbase smart contract is called. In this case:

  • receiver is 0xd2bA3e0000000000000000000000000000000000 (the address of Etherbase)

  • value is equal to 0 because sFUEL isn’t needed

  • data is equal to 0x2f2ff15de0ba7b49edc651b7ad93b374c67f1e9a0d37370168bbb86b81c569ebfa15f046000000000000000000000000d2D2D2D2d2d2d2d2D2d2d2d2D2d2D2d2D2d2D2d2 (grantRole function selector 0x2f2ff15d + ETHER_MANAGER_ROLE id 0xe0ba7b49edc651b7ad93b374c67f1e9a0d37370168bbb86b81c569ebfa15f046 + padded address parameter 0x000000000000000000000000d2D2D2D2d2d2d2d2D2d2d2d2D2d2D2d2D2d2D2d2).

Accordingly, encodedCall is abi.encode(receiver, value, data) and equals:

000000000000000000000000d2ba3e0000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000060
0000000000000000000000000000000000000000000000000000000000000044
2f2ff15de0ba7b49edc651b7ad93b374c67f1e9a0d37370168bbb86b81c569eb
fa15f046000000000000000000000000d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2
d2d2d2d200000000000000000000000000000000000000000000000000000000

IMA

Omitting details, there is a MessageProxyForMainnet smart contract deployed on Ethereum with the function postOutgoingMessage(bytes32 targetChainHash, address targetContract, bytes memory data). Call of this function causes execution of function postMessage of a smart contract with address targetContract on SKALE chain where the hash of its name is targetChainHash.

In this example, postOutgoingMessage receives the following parameters:

  • targetChainHash - 0x7e67eb6444a60ce618f250a380d5b7b32e7b5dbb96b0d43506047b1f15c8f23c - keccak256 hash of SKALE chain name example-chain

  • targetContract - 0xD2c0DeFACe000000000000000000000000000000 address of Marionette smart contract

  • data - encoded call to grantRole function of Etherbase smart contract (see previous section)

Summary

Sending a transaction with data

94489202
7e67eb6444a60ce618f250a380d5b7b32e7b5dbb96b0d43506047b1f15c8f23c
000000000000000000000000d2c0deface000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000060
00000000000000000000000000000000000000000000000000000000000000e0
000000000000000000000000d2ba3e0000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000060
0000000000000000000000000000000000000000000000000000000000000044
2f2ff15de0ba7b49edc651b7ad93b374c67f1e9a0d37370168bbb86b81c569eb
fa15f046000000000000000000000000d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2
d2d2d2d200000000000000000000000000000000000000000000000000000000

from Gnosis SAFE to MessageProxyForMainnet calls

postOutgoingMessage(
    "0x7e67eb6444a60ce618f250a380d5b7b32e7b5dbb96b0d43506047b1f15c8f23c", // SKALE chain name hash
    "0xD2c0DeFACe000000000000000000000000000000" // Marionette address,
    "0x0000000000000000000000000000000000000000000000000000000000000060" +
    "00000000000000000000000000000000000000000000000000000000000000e0" +
    "000000000000000000000000d2ba3e0000000000000000000000000000000000" +
    "0000000000000000000000000000000000000000000000000000000000000000" +
    "0000000000000000000000000000000000000000000000000000000000000060" +
    "0000000000000000000000000000000000000000000000000000000000000044" +
    "2f2ff15de0ba7b49edc651b7ad93b374c67f1e9a0d37370168bbb86b81c569eb" +
    "fa15f046000000000000000000000000d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2" +
    "d2d2d2d200000000000000000000000000000000000000000000000000000000" // encoded call to grantRole of Etherbase
)

In the next step, IMA securely transfers the message to example-chain and triggers execution of the Marionette function:

postMessage(
    {mainnet id}, // source chain
    {Gnosis Safe address}, // message sender address,
    "0x0000000000000000000000000000000000000000000000000000000000000060" +
    "00000000000000000000000000000000000000000000000000000000000000e0" +
    "000000000000000000000000d2ba3e0000000000000000000000000000000000" +
    "0000000000000000000000000000000000000000000000000000000000000000" +
    "0000000000000000000000000000000000000000000000000000000000000060" +
    "0000000000000000000000000000000000000000000000000000000000000044" +
    "2f2ff15de0ba7b49edc651b7ad93b374c67f1e9a0d37370168bbb86b81c569eb" +
    "fa15f046000000000000000000000000d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2" +
    "d2d2d2d200000000000000000000000000000000000000000000000000000000" // encoded call to grantRole of Etherbase
)

Then Marionette checks permissions, decodes the call and executes it. In this case, it calls Etherbase:

grantRole(
    "0xe0ba7b49edc651b7ad93b374c67f1e9a0d37370168bbb86b81c569ebfa15f046", // id of ETHER_MANAGER_ROLE
    "0xd2D2D2D2d2d2d2d2D2d2d2d2D2d2D2d2D2d2D2d2" // target address
)