🎓 A1Learner
🔍
0 of 45 lessons completed

Solidity Cross Chain Security

Cross-chain security is the set of rules that stops a bridge from minting tokens nobody locked. A quorum rule is one such rule, and it means a fixed number of relayers must agree before any mint happens.

A non-relayer approval reverts

Only accounts in the relayer set may approve a mint. A stranger approval fails before anything is recorded.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract QuorumMint {
    mapping(address => bool) public isRelayer;
    mapping(bytes32 => mapping(address => bool)) public approved;
    mapping(bytes32 => uint256) public approvalCount;
    mapping(bytes32 => bool) public executed;
    mapping(address => uint256) public balances;
    uint256 public totalSupply;
    uint256 public quorum;
    constructor(address first, address second, address third, uint256 quorumNeeded) {
        isRelayer[first] = true;
        isRelayer[second] = true;
        isRelayer[third] = true;
        quorum = quorumNeeded;
    }
    function approveMint(address to, uint256 amount) public {
        require(isRelayer[msg.sender], "not a relayer");
        bytes32 id = keccak256(abi.encode(to, amount));
        require(!approved[id][msg.sender], "already approved");
        require(!executed[id], "already executed");
        approved[id][msg.sender] = true;
        approvalCount[id] += 1;
        if (approvalCount[id] >= quorum) {
            executed[id] = true;
            balances[to] += amount;
            totalSupply += amount;
        }
    }
}

One approval does not mint

With a quorum of two, the first relayer approval only records one vote. No tokens exist yet after that single approval.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract QuorumMint {
    mapping(address => bool) public isRelayer;
    mapping(bytes32 => mapping(address => bool)) public approved;
    mapping(bytes32 => uint256) public approvalCount;
    mapping(bytes32 => bool) public executed;
    mapping(address => uint256) public balances;
    uint256 public totalSupply;
    uint256 public quorum;
    constructor(address first, address second, address third, uint256 quorumNeeded) {
        isRelayer[first] = true;
        isRelayer[second] = true;
        isRelayer[third] = true;
        quorum = quorumNeeded;
    }
    function approveMint(address to, uint256 amount) public {
        require(isRelayer[msg.sender], "not a relayer");
        bytes32 id = keccak256(abi.encode(to, amount));
        require(!approved[id][msg.sender], "already approved");
        require(!executed[id], "already executed");
        approved[id][msg.sender] = true;
        approvalCount[id] += 1;
        if (approvalCount[id] >= quorum) {
            executed[id] = true;
            balances[to] += amount;
            totalSupply += amount;
        }
    }
}

A second approval mints

The second relayer approval reaches the quorum of two, so the mint runs. The recipient balance and the supply both read 80.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract QuorumMint {
    mapping(address => bool) public isRelayer;
    mapping(bytes32 => mapping(address => bool)) public approved;
    mapping(bytes32 => uint256) public approvalCount;
    mapping(bytes32 => bool) public executed;
    mapping(address => uint256) public balances;
    uint256 public totalSupply;
    uint256 public quorum;
    constructor(address first, address second, address third, uint256 quorumNeeded) {
        isRelayer[first] = true;
        isRelayer[second] = true;
        isRelayer[third] = true;
        quorum = quorumNeeded;
    }
    function approveMint(address to, uint256 amount) public {
        require(isRelayer[msg.sender], "not a relayer");
        bytes32 id = keccak256(abi.encode(to, amount));
        require(!approved[id][msg.sender], "already approved");
        require(!executed[id], "already executed");
        approved[id][msg.sender] = true;
        approvalCount[id] += 1;
        if (approvalCount[id] >= quorum) {
            executed[id] = true;
            balances[to] += amount;
            totalSupply += amount;
        }
    }
}

Example

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract QuorumMint {
    mapping(address => bool) public isRelayer;
    mapping(bytes32 => mapping(address => bool)) public approved;
    mapping(bytes32 => uint256) public approvalCount;
    mapping(bytes32 => bool) public executed;
    mapping(address => uint256) public balances;
    uint256 public totalSupply;
    uint256 public quorum;
    constructor(address first, address second, address third, uint256 quorumNeeded) {
        isRelayer[first] = true;
        isRelayer[second] = true;
        isRelayer[third] = true;
        quorum = quorumNeeded;
    }
    function approveMint(address to, uint256 amount) public {
        require(isRelayer[msg.sender], "not a relayer");
        bytes32 id = keccak256(abi.encode(to, amount));
        require(!approved[id][msg.sender], "already approved");
        require(!executed[id], "already executed");
        approved[id][msg.sender] = true;
        approvalCount[id] += 1;
        if (approvalCount[id] >= quorum) {
            executed[id] = true;
            balances[to] += amount;
            totalSupply += amount;
        }
    }
}

Key points

  • Cross-chain security: Cross-chain security is the set of rules that stops a bridge from minting tokens nobody locked.
  • Quorum: A quorum is a fixed number of approvals that must be reached before an action happens.
  • Relayer set: A relayer set is the group of trusted accounts allowed to approve a mint.
  • Unbacked mint: An unbacked mint is a mint with no locked value behind it.
  • Compromised relayer: A compromised relayer is a trusted account an attacker controls.
  • Restatement: A bridge stays safe when no single relayer can mint alone and a fixed quorum must agree first.

In this example, the first relayer approves a mint of 50, the same relayer trying again fails, and the second relayer approval reaches the quorum. Only then do the 50 tokens appear.

Note

A quorum of two out of three still fails if two relayers are compromised together, so the quorum size is a trade-off, not a guarantee.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Remix IDE

Exercise

A bridge needs a quorum of two relayers. One relayer has approved a mint. What is true?