IMA Access Control

Introduction

The SKALE Chain owner has special permissions to configure SKALE Chain access control. The owner may choose a "permission-by-owner" policy, selectively restricting access to only certain users and/or tokens, or may choose a permissionless policy, enabling any user and/or tokens. This access control includes several features:

SKALE Chain FUEL
  • controlling how users access SKALE Chain FUEL (sFUEL) to perform transactions.

IMA token exchange
  • controlling which tokens transfer between Mainnet and SKALE Chains.

This document describes how SKALE Chain owners can set permissions for the IMA Bridge. There are two general categories:

  • Owner permissions for IMA Mainnet

  • Owner permissions for IMA SKALE Chain

There are several IMA SKALE Chain roles that can be granted by the owner, these include:

  • CHAIN_CONNECTOR_ROLE

  • EXTRA_CONTRACT_REGISTRAR_ROLE

  • CONSTANT_SETTER_ROLE (MessageProxy)

  • CONSTANT_SETTER_ROLE (CommunityLocker)

  • REGISTRAR_ROLE

  • AUTOMATIC_DEPLOY_ROLE

  • TOKEN_REGISTRAR_ROLE

Owner IMA Mainnet Permissions

Table 1. Owner IMA Mainnet Permissions
Contracts Functions Description

DepositBoxERC20 DepositBoxERC721 DepositBoxERC1155

enableWhitelist disableWhitelist

Enable/disable whitelist (default: enabled). Whitelist allows only specific tokens to be used with the IMA Bridge.

addERC20TokenByOwner addERC721TokenByOwner addERC1155TokenByOwner

Allows owner to manually map tokens to the bridge.

DepositBoxEth DepositBoxERC20 DepositBoxERC721 DepositBoxERC1155

getFunds

If the kill operation is executed, allows owner to return tokens to end-users.

MessageProxyForMainnet

registerExtraContract removeExtraContract

Allows owner to add/remove extra contracts to/from the IMA Bridge.

Linker

allowInterchainConnections

Allows owner to connect other chains (that they own) to the IMA bridge.

kill

Allows owner and contract deployer to kill the SKALE Chain IMA Bridge and allow owner to execute getFunds(). Both roles must call this function to execute. Reserved only for extraordinary situations.

IMA SKALE Chain Permissions

Table 2. IMA SKALE Chain Permissions
Contracts Functions Description

ProxyAdmin

Allows owner to upgrade contracts.

MessageProxyForSchain

grantRole

Allows owner to grant CHAIN_CONNECTOR_ROLE, EXTRA_CONTRACT_REGISTRAR_ROLE, REGISTRAR_ROLE, AUTOMATIC_DEPLOY_ROLE, TOKEN_REGISTRAR_ROLE and CONSTANT_SETTER_ROLE.

addConnectedChain removeConnectedChain

Allows owner or CHAIN_CONNECTOR_ROLE to add/remove a chain.

registerExtraContract removeExtraContract

Allows owner or EXTRA_CONTRACT_REGISTRAR_ROLE to add/remove an extra SKALE Chain contract to/from the IMA Bridge.

setNewGasLimit

Allows owner or CONSTANT_SETTER_ROLE to set a new gas limit for MessageProxy outgoing messages.

TokenManagerEth

setEthErc20Address

Allows owner to set a new address for ETHERC20 (wrapped ETH) on SKALE.

TokenManagerERC20 TokenManagerERC721 TokenManagerERC1155

grantRole

Allows owner to grant AUTOMATIC_DEPLOY_ROLE, and TOKEN_REGISTRAR_ROLE.

addTokenManager removeTokenManager

Allows owner to add/remove other TokenManagers to the IMA Bridge.

changeDepositBoxAddress

Allows owner to set a new DepositBox address.

enableAutomaticDeploy disableAutomaticDeploy

Allows owner or AUTOMATIC_DEPLOY_ROLE to enable/disable automatic deployment (default: disabled).

addERC20TokenByOwner addERC721TokenByOwner addERC1155TokenByOwner

Allows owner or TOKEN_REGISTRAR_ROLE to manually map tokens to the SKALE Chain side of the IMA Bridge.

TokenManagerLinker

registerTokenManager removeTokenManager

Allows owner or REGISTRAR_ROLE to add/remove TokenManager contracts.

connectSchain disconnectSchain

Allows owner or REGISTRAR_ROLE to connect/disconnect SKALE Chains.

CommunityLocker

setTimeLimitPerMessage

Allows owner or CONSTANT_SETTER_ROLE to set a new rate limit for exit messages. (default: 5 minutes per SKALE Chain exit message).

Enable or Disable the Whitelist

The token whitelist allows the SKALE chain owner to allow token exchange through IMA and a SKALE chain. For example, a decentralized exchange may prefer to disable the whitelist to allow any token transfer. A dApp using only one token may enable the whitelist to allow only a single token exchange.

To disable the whitelist, the owner executes disableWhitelist in DepositBoxERC20/721/1155 contracts, and pass in the schainName as the only argument. For example:

  • Python

  • Web3.js sync

  • Web3.js async

  • Hardhat TS

deposit_box_erc20 = self._get_contract_on_mainnet('deposit_box_erc20')
disable = deposit_box_erc20.encodeABI(fn_name="disableWhitelist", args=[schainName])

signed_txn = self.web3_mainnet.eth.account.signTransaction(dict(
        nonce=self.web3_mainnet.eth.getTransactionCount(sender_address),
        gasPrice=self.web3_mainnet.eth.gasPrice,
        gas=200000,
        to=deposit_box_erc20.address,
        value=0,
        data = disable
    ),
    from_key)

self.web3_mainnet.eth.sendRawTransaction(signed_txn.rawTransaction)
const Web3 = require('web3');
const Tx = require('ethereumjs-tx');

let rinkebyABIs = require("[YOUR_SKALE_ABIs_ON_RINKEBY]");
let privateKey = new Buffer('[YOUR_PRIVATE_KEY]', 'hex')
let account = "[YOUR_ACCOUNT_ADDRESS]";
let rinkeby = "[RINKEBY_ENDPOINT]";
let schainName = "[YOUR_SKALE_CHAIN_NAME]";
let chainID = "[ETHEREUM_CHAIN_ID]";

const depositBoxERC20Address = rinkebyABIs.deposit_box_erc20_address;
const depositBoxERC20ABI = rinkebyABIs.deposit_box_erc20_abi;

const web3 = new Web3(rinkeby);

let contract = new web3.eth.Contract(depositBoxERC20ABI, depositBoxERC20Address);

/*
 * prepare the smart contract function
 * disableWhitelist(string chainName)
 */
let disableWhitelist = contract.methods
  .disableWhitelist(
    schainName
    )
  .encodeABI();

//get nonce
web3.eth.getTransactionCount(account).then(nonce => {

  //create raw transaction to send 1 ETH
  const rawTx = {
    chainId: chainId,
    from: account,
    nonce: "0x" + nonce.toString(16),
    data: disableWhitelist,
    to: depositBoxERC20Address,
    gas: 200000,
    gasPrice: 10000000000
  };

  //sign transaction
  const tx = new Tx(rawTx);
  tx.sign(privateKey);

  //serialize transaction
  const serializedTx = tx.serialize();

  //send signed transaction
  web3.eth
    .sendSignedTransaction("0x" + serializedTx.toString("hex"))
    .on("receipt", receipt => {
      //record receipt to console
      console.log(receipt);
    })
    .catch(console.error);
});
const Web3 = require('web3');
const Tx = require('ethereumjs-tx');

let rinkebyABIs = require("[YOUR_SKALE_ABIs_ON_RINKEBY]");
let privateKey = new Buffer('[YOUR_PRIVATE_KEY]', 'hex')
let account = "[YOUR_ACCOUNT_ADDRESS]";
let rinkeby = "[RINKEBY_ENDPOINT]";
let skaleChainName = "[YOUR_SKALE_CHAIN_NAME]";
let chainID = "[ETHEREUM_CHAIN_ID]";

const depositBoxERC20Address = rinkebyABIs.deposit_box_erc20_address;
const depositBoxERC20ABI = rinkebyABIs.deposit_box_erc20_abi;

const web3 = new Web3(rinkeby);

let depositBoxERC20 = new web3.eth.Contract(depositBoxERC20ABI, depositBoxERC20Address);

async function disableWhitelist(contract, chainName) {
    const disableWhitelistMethod = contract.methods.disableWhitelist(chainName).encodeABI();
    const nonce = await web3.eth.getTransactionCount(account);
    const rawTX = {
        chainId: chainId,
        from: account,
        nonce: "0x" + nonce.toString(16),
        data: disableWhitelistMethod,
        to: contract.address,
        gas: 200000,
        gasPrice: 100000000000
    };
    const tx = new Tx(rawTx);
    tx.sign(privateKey);
    const serializedTx = tx.serialize();
    const txReceipt = await web3.eth.sendSignedTransaction("0x" + serializedTx.toString("hex"));
    console.log("Receipt:", txReceipt);
}

// run function
disableWhitelist(depositBoxERC20, skaleChainName);
// add account and network to the hardhat config file

// run from the console:
// npx hardhat run PATH_TO_SCRIPT --network rinkeby

import { ethers } from "hardhat";
import { promises as fs } from "fs";

const abiFilename = "[YOUR_SKALE_ABIs_ON_RINKEBY]"
const skaleChainName = "[YOUR_SKALE_CHAIN_NAME]";

async function disableWhitelist(schainName: string) {
    const abi = JSON.parse(await fs.readFile(abiFilename, "utf-8"));
    const [ deployer ] = await ethers.getSigners();
    const depositBoxERC20Address = abi["deposit_box_erc20_address"];
    const depositBoxERC20Abi = abi["deposit_box_erc20_abi"];
    const depositBoxERC20Factory = await ethers.Contract(depositBoxERC20Address, depositBoxERC20Abi, deployer);
    const txReceipt = await( await(depositBoxERC20.disableWhitelist(schainName))).wait();
    console.log("Receipt:", txReceipt);
}

if (require.main === module) {
    disableWhitelist(skaleChainName)
        .then(() => process.exit(0))
        .catch(error => {
            console.error(error);
            process.exit(1);
        });
}

To re-enable the whitelist:

  • Python

  • Web3.js sync

  • Web3.js async

  • Hardhat TS

deposit_box_erc20 = self._get_contract_on_mainnet('deposit_box_erc20')
enable = deposit_box_erc20.encodeABI(fn_name="enableWhitelist", args=[schainName])

signed_txn = self.web3_mainnet.eth.account.signTransaction(dict(
        nonce=self.web3_mainnet.eth.getTransactionCount(sender_address),
        gasPrice=self.web3_mainnet.eth.gasPrice,
        gas=200000,
        to=deposit_box_erc20.address,
        value=0,
        data = enable
    ),
    from_key)

self.web3_mainnet.eth.sendRawTransaction(signed_txn.rawTransaction)
const Web3 = require('web3');
const Tx = require('ethereumjs-tx');

let rinkebyABIs = require("[YOUR_SKALE_ABIs_ON_RINKEBY]");
let privateKey = new Buffer('[YOUR_PRIVATE_KEY]', 'hex')
let account = "[YOUR_ACCOUNT_ADDRESS]";
let rinkeby = "[RINKEBY_ENDPOINT]";
let schainName = "[YOUR_SKALE_CHAIN_NAME]";
let chainID = "[ETHEREUM_CHAIN_ID]";

const depositBoxERC20Address = rinkebyABIs.deposit_box_erc20_address;
const depositBoxERC20ABI = rinkebyABIs.deposit_box_erc20_abi;

const web3 = new Web3(rinkeby);

let contract = new web3.eth.Contract(depositBoxERC20ABI, depositBoxERC20Address);

/*
 * prepare the smart contract function
 * enableWhitelist(string chainName)
 */
let enableWhitelist = contract.methods
  .enableWhitelist(
    schainName
    )
  .encodeABI();

//get nonce
web3.eth.getTransactionCount(account).then(nonce => {

  //create raw transaction to send 1 ETH
  const rawTx = {
    chainId: chainId,
    from: account,
    nonce: "0x" + nonce.toString(16),
    data: enableWhitelist,
    to: depositBoxERC20Address,
    gas: 200000,
    gasPrice: 100000000000
  };

  //sign transaction
  const tx = new Tx(rawTx);
  tx.sign(privateKey);

  //serialize transaction
  const serializedTx = tx.serialize();

  //send signed transaction
  web3.eth
    .sendSignedTransaction("0x" + serializedTx.toString("hex"))
    .on("receipt", receipt => {
      //record receipt to console
      console.log(receipt);
    })
    .catch(console.error);
});
const Web3 = require('web3');
const Tx = require('ethereumjs-tx');

let rinkebyABIs = require("[YOUR_SKALE_ABIs_ON_RINKEBY]");
let privateKey = new Buffer('[YOUR_PRIVATE_KEY]', 'hex')
let account = "[YOUR_ACCOUNT_ADDRESS]";
let rinkeby = "[RINKEBY_ENDPOINT]";
let skaleChainName = "[YOUR_SKALE_CHAIN_NAME]";
let chainID = "[ETHEREUM_CHAIN_ID]";

const depositBoxERC20Address = rinkebyABIs.deposit_box_erc20_address;
const depositBoxERC20ABI = rinkebyABIs.deposit_box_erc20_abi;

const web3 = new Web3(rinkeby);

let depositBoxERC20 = new web3.eth.Contract(depositBoxERC20ABI, depositBoxERC20Address);

async function enableWhitelist(contract, chainName) {
    const enableWhitelistMethod = contract.methods.enableWhitelist(chainName).encodeABI();
    const nonce = await web3.eth.getTransactionCount(account);
    const rawTX = {
        chainId: chainId,
        from: account,
        nonce: "0x" + nonce.toString(16),
        data: enableWhitelistMethod,
        to: contract.address,
        gas: 200000,
        gasPrice: 100000000000
    };
    const tx = new Tx(rawTx);
    tx.sign(privateKey);
    const serializedTx = tx.serialize();
    const txReceipt = await web3.eth.sendSignedTransaction("0x" + serializedTx.toString("hex"));
    console.log("Receipt:", txReceipt);
}

// run function
enableWhitelist(depositBoxERC20, skaleChainName);
// add account and network to the hardhat config file

// run from the console:
// npx hardhat run PATH_TO_SCRIPT --network rinkeby

import { ethers } from "hardhat";
import { promises as fs } from "fs";

const abiFilename = "[YOUR_SKALE_ABIs_ON_RINKEBY]"
const skaleChainName = "[YOUR_SKALE_CHAIN_NAME]";

async function enableWhitelist(schainName: string) {
    const abi = JSON.parse(await fs.readFile(abiFilename, "utf-8"));
    const [ deployer ] = await ethers.getSigners();
    const depositBoxERC20Address = abi["deposit_box_erc20_address"];
    const depositBoxERC20Abi = abi["deposit_box_erc20_abi"];
    const depositBoxERC20Factory = await ethers.Contract(depositBoxERC20Address, depositBoxERC20Abi, deployer);
    const txReceipt = await( await(depositBoxERC20.enableWhitelist(schainName))).wait();
    console.log("Receipt:", txReceipt);
}

if (require.main === module) {
    enableWhitelist(skaleChainName)
        .then(() => process.exit(0))
        .catch(error => {
            console.error(error);
            process.exit(1);
        });
}

Adding tokens with Enabled Whitelist

If the whitelist is enabled, the SKALE chain owner must authorize each token for the schain.

You can use addERC20TokenByOwner, addERC721TokenByOwner, and addERC1155TokenByOwner functions available in DepositBoxERC20, DepositBoxERC721, DepositBox1155, and TokenManagerERC20, TokenManagerERC721, TokenManagerERC1155 contracts on both Mainnet and each SKALE chain.

The recommended process is

  1. Deploy the ERC20/ERC721/ERC1155 token contract on the SKALE chain.

  2. Add the token to TokenManagerERC20/TokenManagerERC721/TokenManager1155 on the SKALE chain.

  3. Grant the token’s minter role to TokenManagerERC20/TokenManagerERC721/TokenManager1155 on the SKALE chain.

  4. Add the token to DepositBoxERC20/DepositBoxERC721/DepositBox1155 on Mainnet.

Adding tokens to TokenManager

Adding ERC20

  • Python

token_manager_erc20 = self._get_contract_on_schain('token_manager_erc20')
addERC20Schain = token_manager_erc20.encodeABI(fn_name="addERC20TokenByOwner", args=[erc20MainnetAddress, erc20SchainAddress])

signed_txn = self.web3_schain.eth.account.signTransaction(dict(
        nonce=self.web3_schain.eth.getTransactionCount(sender_address),
        gasPrice=self.web3_schain.eth.gasPrice,
        gas=200000,
        to=token_manager_erc20.address,
        value=0,
        data = addERC20Schain
    ),
    from_key)

self.web3_schain.eth.sendRawTransaction(signed_txn.rawTransaction)

Adding ERC721

  • Python

token_manager_erc721 = self._get_contract_on_schain('token_manager_erc721')
addERC721Schain = token_manager_erc721.encodeABI(fn_name="addERC721TokenByOwner", args=[erc721MainnetAddress, erc721SchainAddress])

signed_txn = self.web3_schain.eth.account.signTransaction(dict(
        nonce=self.web3_schain.eth.getTransactionCount(sender_address),
        gasPrice=self.web3_schain.eth.gasPrice,
        gas=200000,
        to=token_manager_erc721.address,
        value=0,
        data = addERC721Schain
    ),
    from_key)

self.web3_schain.eth.sendRawTransaction(signed_txn.rawTransaction)

Assign Schain TokenManager as Minter and Burner role

You need to assign TokenManagerERC20/TokenManagerERC721/TokenManagerERC1155 as the minter and burner for the deployed token on the schain. For AccessControl supported ERC20/ERC721/ERC1155, you can use apply the following pseudocode:

  • Python

newERC20 = deployERC20(deployer)
minterRoleERC20 = newERC20.MINTER_ROLE()
newERC20.grantRole(minterRoleERC20, token_manager_erc20.address)

Adding tokens to DepositBox

addERC20TokenByOwner, addERC721TokenByOwner, addERC1155TokenByOwner on the DepositBox takes 2 arguments: schainName and erc20Mainnet/erc721Mainnet/erc1155Mainnet address.

Adding ERC20

  • Python

deposit_box_erc20 = self._get_contract_on_mainnet('deposit_box_erc20')
addERC20Mainnet = deposit_box_erc20.encodeABI(fn_name="addERC20TokenByOwner", args=[schainName, erc20MainnetAddress])

signed_txn = self.web3_mainnet.eth.account.signTransaction(dict(
        nonce=self.web3_mainnet.eth.getTransactionCount(sender_address),
        gasPrice=self.web3_mainnet.eth.gasPrice,
        gas=200000,
        to=deposit_box_erc20.address,
        value=0,
        data = addERC20Mainnet
    ),
    from_key)

self.web3_mainnet.eth.sendRawTransaction(signed_txn.rawTransaction)

Adding ERC721

addERC721TokenByOwner on the DepositBox takes 2 arguments: schainName and erc721Mainnet address.
  • Python

deposit_box_erc721 = self._get_contract_on_mainnet('deposit_box_erc721')
addERC721Mainnet = deposit_box_erc721.encodeABI(fn_name="addERC721TokenByOwner", args=[schainName, erc721MainnetAddress])

signed_txn = self.web3_mainnet.eth.account.signTransaction(dict(
        nonce=self.web3_mainnet.eth.getTransactionCount(sender_address),
        gasPrice=self.web3_mainnet.eth.gasPrice,
        gas=200000,
        to=deposit_box_erc721.address,
        value=0,
        data = addERC20Mainnet
    ),
    from_key)

self.web3_mainnet.eth.sendRawTransaction(signed_txn.rawTransaction)

Automatic deployment

Automatic deployment is disabled by default and requires that the SKALE Chain owner deploys the token contract on the SKALE Chain.

If enabled, then tokens are automatically deployed on the schain by the TokenFactory contract after a token is received through DepositBox on mainnet.

There are several limits to using automatic deployment. When using this method: