您现在的位置:kastop>> Kas信息 Kaspa应用>>正文内容

Kasplex zkEVM典型合约代码部署分析点评

代码部署地址:

https://explorer.kasplex.org/token/0xb7a95035618354D9ADFC49Eca49F38586B624040?tab=contract_source_code



pragma solidity ^0.8.0;


interface IERC20 {

    function totalSupply() external view returns (uint256);


    function balanceOf(address account) external view returns (uint256);


    function transfer(address to, uint256 amount) external returns (bool);


    function allowance(

        address owner,

        address spender

    ) external view returns (uint256);


    function approve(address spender, uint256 amount) external returns (bool);


    function transferFrom(

        address from,

        address to,

        uint256 amount

    ) external returns (bool);


    event Transfer(address indexed from, address indexed to, uint256 value);

    event Approval(

        address indexed owner,

        address indexed spender,

        uint256 value

    );

}


contract BridgedToken is IERC20 {

    mapping(address => uint256) private _balances;

    mapping(address => mapping(address => uint256)) private _allowances;


    uint256 private _totalSupply;

    uint256 public maxSupply;

    string public name;

    string public symbol;

    uint8 public decimals;


    address public bridgeAddress;


    event BridgeAddressChanged(

        address indexed oldBridge,

        address indexed newBridge

    );

    event Mint(address indexed to, uint256 amount);

    event Burn(address indexed from, uint256 amount);


    modifier onlyBridge() {

        require(msg.sender == bridgeAddress, "Only bridge");

        _;

    }


    constructor(

        string memory _name,

        string memory _symbol,

        uint8 _decimals,

        uint256 _maxSupply,

        address _bridgeAddress

    ) {

        name = _name;

        symbol = _symbol;

        decimals = _decimals;

        maxSupply = _maxSupply;

        bridgeAddress = _bridgeAddress;

    }


    function totalSupply() public view override returns (uint256) {

        return _totalSupply;

    }


    function balanceOf(address account) public view override returns (uint256) {

        return _balances[account];

    }


    function transfer(

        address to,

        uint256 amount

    ) public override returns (bool) {

        address owner = msg.sender;

        _transfer(owner, to, amount);

        return true;

    }


    function allowance(

        address owner,

        address spender

    ) public view override returns (uint256) {

        return _allowances[owner][spender];

    }


    function approve(

        address spender,

        uint256 amount

    ) public override returns (bool) {

        address owner = msg.sender;

        _approve(owner, spender, amount);

        return true;

    }


    function transferFrom(

        address from,

        address to,

        uint256 amount

    ) public override returns (bool) {

        address spender = msg.sender;

        _spendAllowance(from, spender, amount);

        _transfer(from, to, amount);

        return true;

    }


    function mint(address to, uint256 amount) public onlyBridge {

        require(to != address(0), "Cannot mint to zero address");

        require(amount > 0, "Amount must be greater than zero");

        require(

            _totalSupply + amount <= maxSupply,

            "Minting would exceed max supply"

        );


        _totalSupply += amount;

        _balances[to] += amount;


        emit Transfer(address(0), to, amount);

        emit Mint(to, amount);

    }


    function burn(address from, uint256 amount) public onlyBridge {

        require(from != address(0), "Cannot burn from zero address");

        require(amount > 0, "Amount must be greater than zero");

        require(_balances[from] >= amount, "Insufficient balance to burn");


        _balances[from] -= amount;

        _totalSupply -= amount;


        emit Transfer(from, address(0), amount);

        emit Burn(from, amount);

    }


    function setBridgeAddress(address newBridge) public onlyBridge {

        require(newBridge != address(0), "New bridge address cannot be zero");

        address oldBridge = bridgeAddress;

        bridgeAddress = newBridge;


        emit BridgeAddressChanged(oldBridge, newBridge);

    }


    function _transfer(address from, address to, uint256 amount) internal {

        require(from != address(0), "Transfer from zero address");

        require(to != address(0), "Transfer to zero address");


        uint256 fromBalance = _balances[from];

        require(fromBalance >= amount, "Transfer amount exceeds balance");


        unchecked {

            _balances[from] = fromBalance - amount;

            _balances[to] += amount;

        }


        emit Transfer(from, to, amount);

    }


    function _approve(address owner, address spender, uint256 amount) internal {

        require(owner != address(0), "Approve from zero address");

        require(spender != address(0), "Approve to zero address");


        _allowances[owner][spender] = amount;

        emit Approval(owner, spender, amount);

    }


    function _spendAllowance(

        address owner,

        address spender,

        uint256 amount

    ) internal {

        uint256 currentAllowance = allowance(owner, spender);

        if (currentAllowance != type(uint256).max) {

            require(currentAllowance >= amount, "Insufficient allowance");

            unchecked {

                _approve(owner, spender, currentAllowance - amount);

            }

        }

    }

}


这是一个实现了 ERC-20 标准的跨链桥接代币(Bridged Token) 合约。它的主要目的是允许一个专门的桥接地址 (bridgeAddress) 进行代币的铸造(mint)和销毁(burn),通常用于在不同区块链之间转移资产。

总体而言,代码结构清晰,遵循了大部分 ERC-20 标准和安全实践。然而,在以下几个方面仍存在潜在问题和改进空间:



潜在安全问题与关键漏洞



1. 桥接地址的单点故障风险(High Risk)


代币的全部发行(mint)和销毁(burn)权力都集中在 bridgeAddress 上,并且该地址可以通过 setBridgeAddress 函数进行更改。

  • 问题所在:setBridgeAddress 函数只被 onlyBridge 修饰符保护。这意味着当前的 bridgeAddress 拥有随时将权力转移给任何其他地址的权限。如果 bridgeAddress 被盗用(私钥泄露),攻击者可以:

    1. 通过 setBridgeAddress 转移控制权给自己。

    2. 通过 mint 凭空铸造代币,直至达到 maxSupply

    3. 通过 burn 销毁任何用户的代币(但需要先让用户授权,或从自己的地址销毁)。

  • 改进建议: 实施一个更安全的权限管理机制,例如:

    • 多重签名(Multi-sig):要求多个受信任的地址共同批准 setBridgeAddress 交易。

    • 时间锁定(Timelock):在更改桥接地址之前,设置一个时间延迟,给社区或原始所有者足够的时间来发现并响应恶意更改。


2. transferFrom 函数中的无限授权问题(Medium Risk)


transferFrom 的底层函数 _spendAllowance 中,存在处理 type(uint256).max(无限授权)时的潜在安全漏洞

  • 问题所在:currentAllowance 是无限授权(type(uint256).max)时,_spendAllowance 函数不会减少授权额度。这本身是标准行为,但通常不应进入 unchecked 块。

    Solidity

    if (currentAllowance != type(uint256).max) {
       require(currentAllowance >= amount, "Insufficient allowance");
       unchecked {
           _approve(owner, spender, currentAllowance - amount); // 🚨 这里
       }
    }

    虽然 Solidity 0.8.0 默认开启了溢出检查,但在 unchecked 块中,如果 currentAllowance 小于 amountcurrentAllowance - amount 可能会下溢。虽然前面的 require 语句防止了下溢,但将核心授权逻辑放在 unchecked 块中增加了未来的维护风险

  • 改进建议:unchecked 块移除或确保其仅包含性能关键且已验证无溢出的操作。在这个特定的 _spendAllowance 逻辑中,移除 unchecked 块更为安全和清晰。



编码实践和优化建议



3. 铸造和销毁事件的参数(Minor Issue)


mintburn 函数发出的 Transfer 事件,遵循了 ERC-20 标准中将零地址 address(0) 用于铸造和销毁的惯例。

  • mint: emit Transfer(address(0), to, amount);

  • burn: emit Transfer(from, address(0), amount);

  • 问题所在: 这符合标准,但如果你想严格区分普通转移和铸造/销毁,通常建议只依赖自定义的 MintBurn 事件。由于你已经定义了自定义事件,这不是严格的错误,但可能会让某些解析器感到混淆。


4. _transfer 函数中的 unchecked 块(Minor Issue)


_transfer 函数使用 unchecked 块来执行余额的加减操作:

Solidity

unchecked {
   _balances[from] = fromBalance - amount;
   _balances[to] += amount;
}
  • 问题所在: 尽管在前面的 require(fromBalance >= amount, ...) 检查已经确保了减法不会下溢,但加法 (_balances[to] += amount) 仍然有理论上的溢出风险(如果 _balances[to] + amount 超过 22561)。对于大多数实际应用来说,这不可能发生,但考虑到 Solidity 0.8.0 默认提供了安全数学,建议移除 unchecked,让编译器处理安全检查,除非你确认这是一个瓶颈,并且愿意承担潜在风险。



总结


该合约是一个可用的桥接代币实现,但其最主要的风险在于对 bridgeAddress 的中心化控制。在进行部署前,强烈建议在 setBridgeAddress 函数中增加多重签名或时间锁机制,以保护代币供应的安全。






感动 同情 无聊 愤怒 搞笑 难过 高兴 路过
【字体: 】【收藏】【打印文章】 【 打赏 】 【查看评论

相关文章

    没有相关内容