Using Gnosis SAFE Multisig
You can use Ethereum-based Gnosis SAFE with IMA for SKALE chain administration functions.
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
-
Go to Gnosis Safe UI and press the New transaction →
Contract interaction
. -
Enable the checkbox
Use custom data (hex encoded)
. -
Put the IMA address for
message_proxy_mainnet_address
to theContract address
field. For Ethereum this is0x8629703a9903515818C2FeB45a6f6fA5df8Da404
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. -
Enter
0
in theValue
field. -
Copy the hex string obtained in the previous step from
multisigwallet-cli
to theData
field. -
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
is0xd2bA3e0000000000000000000000000000000000
(the address ofEtherbase
) -
value is equal to
0
because sFUEL isn’t needed -
data is equal to
0x2f2ff15de0ba7b49edc651b7ad93b374c67f1e9a0d37370168bbb86b81c569ebfa15f046000000000000000000000000d2D2D2D2d2d2d2d2D2d2d2d2D2d2D2d2D2d2D2d2
(grantRole
function selector0x2f2ff15d
+ETHER_MANAGER_ROLE
id0xe0ba7b49edc651b7ad93b374c67f1e9a0d37370168bbb86b81c569ebfa15f046
+ padded address parameter0x000000000000000000000000d2D2D2D2d2d2d2d2D2d2d2d2D2d2D2d2D2d2D2d2
).
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 nameexample-chain
-
targetContract -
0xD2c0DeFACe000000000000000000000000000000
address ofMarionette
smart contract -
data - encoded call to
grantRole
function ofEtherbase
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
)