Michael.W基于Foundry精读Openzeppelin第45期——ERC20FlashMint.sol

news2024/11/20 20:43:41

Michael.W基于Foundry精读Openzeppelin第45期——ERC20FlashMint.sol

      • 0. 版本
        • 0.1 ERC20FlashMint.sol
      • 1. 目标合约
      • 2. 代码精读
        • 2.1 maxFlashLoan(address token)
        • 2.2 flashFee(address token, uint256 amount)
        • 2.3 flashLoan(IERC3156FlashBorrower receiver, address token, uint256 amount, bytes calldata data)

0. 版本

[openzeppelin]:v4.8.3,[forge-std]:v1.5.6

0.1 ERC20FlashMint.sol

Github: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.8.3/contracts/token/ERC20/extensions/ERC20FlashMint.sol

ERC20FlashMint库是ERC20的拓展,也是关于闪电贷ERC3156的实现。ERC20FlashMint库在ERC20的基础上实现了IERC3156FlashLender接口,在token层面上支持了闪电贷功能。但是该库默认没有闪电贷手续费,开发者可以通过重写flashFee()方法来自定义手续费计算逻辑。

EIP3156详情参见:https://eips.ethereum.org/EIPS/eip-3156

1. 目标合约

继承ERC20FlashMint合约:

Github: https://github.com/RevelationOfTuring/foundry-openzeppelin-contracts/blob/master/src/token/ERC20/extensions/MockERC20FlashMint.sol

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import "openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20FlashMint.sol";

contract MockERC20FlashMint is ERC20FlashMint {
    bool private _customizedFlashFeeAndReceiver;

    constructor(
        string memory name,
        string memory symbol,
        address richer,
        uint totalSupply
    )
    ERC20(name, symbol)
    {
        _mint(richer, totalSupply);
    }

    function customizedFlashFeeAndReceiver() external {
        _customizedFlashFeeAndReceiver = true;
    }

    // customized flash fee 10% amount
    function _flashFee(address token, uint amount) internal view override returns (uint) {
        return _customizedFlashFeeAndReceiver ?
            amount / 10 : ERC20FlashMint._flashFee(token, amount);
    }

    // customized fee receiver address(1024)
    function _flashFeeReceiver() internal view override returns (address) {
        return _customizedFlashFeeAndReceiver ?
            address(1024) : ERC20FlashMint._flashFeeReceiver();
    }
}

全部foundry测试合约:

Github: https://github.com/RevelationOfTuring/foundry-openzeppelin-contracts/blob/master/test/token/ERC20/extensions/ERC20FlashMint/ERC20FlashMint.t.sol

测试使用的物料合约:

Github: https://github.com/RevelationOfTuring/foundry-openzeppelin-contracts/blob/master/test/token/ERC20/extensions/ERC20FlashMint/ERC3156FlashBorrower.sol

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import "openzeppelin-contracts/contracts/interfaces/IERC3156FlashBorrower.sol";
import "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";

contract ERC3156FlashBorrower is IERC3156FlashBorrower {
    bytes32 private constant _RETURN_VALUE = keccak256("ERC3156FlashBorrower.onFlashLoan");

    bool private _enableApprove;
    bool private _enableValidReturnValue;

    event ParamsIn(
        address initiator,
        address token,
        uint256 amount,
        uint256 fee,
        bytes data
    );

    event Monitor(
        address owner,
        uint balance,
        uint totalSupply
    );

	// implementation of IERC3156FlashBorrower.onFlashLoan()
    function onFlashLoan(
        address initiator,
        address token,
        uint256 amount,
        uint256 fee,
        bytes calldata data
    ) external returns (bytes32){
        IERC20 erc20Token = IERC20(token);
        // show the params input
        emit ParamsIn(
            initiator,
            token,
            amount,
            fee,
            data
        );

        // show the token status during IERC3156FlashBorrower.onFlashLoan()
        emit Monitor(
            address(this),
            erc20Token.balanceOf(address(this)),
            erc20Token.totalSupply()
        );

        if (data.length != 0) {
            (bool ok,) = token.call(data);
            require(ok, "fail to call");
        }

        if (_enableApprove) {
            erc20Token.approve(token, amount + fee);
        }

        return _enableValidReturnValue ? _RETURN_VALUE : bytes32(0);
    }

    function flipApprove() external {
        _enableApprove = !_enableApprove;
    }

    function flipValidReturnValue() external {
        _enableValidReturnValue = !_enableValidReturnValue;
    }
}

2. 代码精读

2.1 maxFlashLoan(address token)

IERC3156FlashLender中的标准方法实现,返回输入token最大可借贷的数量。

    // 如果IERC3156FlashBorrower.onFlashLoan()方法返回该常量值表示该方法执行有效
    bytes32 private constant _RETURN_VALUE = keccak256("ERC3156FlashBorrower.onFlashLoan");
		
	// 参数:
	// - token: 要借出token的地址
    function maxFlashLoan(address token) public view virtual override returns (uint256) {
    	// 如果传入的token地址为本ERC20地址,返回最大可借贷数量为type(uint256).max-当前本ERC20的总供应量。否则返回0
        return token == address(this) ? type(uint256).max - ERC20.totalSupply() : 0;
    }

foundry代码验证:

contract ERC20FlashMintTest is Test {
    MockERC20FlashMint private _testing = new MockERC20FlashMint("test name", "test symbol", address(this), 10000);

    function test_MaxFlashLoan() external {
        uint totalSupply = _testing.totalSupply();
        assertEq(totalSupply, 10000);
        // query for self
        assertEq(_testing.maxFlashLoan(address(_testing)), type(uint).max - totalSupply);
        // query for other
        assertEq(_testing.maxFlashLoan(address(0)), 0);
    }
}
2.2 flashFee(address token, uint256 amount)

IERC3156FlashLender中的标准方法实现,返回借出数量为amount、地址为token的ERC20需要支付的手续费。该方法内部调用internal方法_flashFee(),可以在子合约中重写_flashFee()方法来实现需要的手续费计算逻辑。

	// 参数:
    // - token: 闪电贷的token地址
    // - amount: 闪电贷的token数量
    function flashFee(address token, uint256 amount) public view virtual override returns (uint256) {
    	// 要求token为本ERC20合约地址
        require(token == address(this), "ERC20FlashMint: wrong token");
        // 调用internal方法_flashFee(),返回对应手续费数量
        return _flashFee(token, amount);
    }
    
    // internal方法,返回具体的借出数量为amount、地址为token的ERC20需要支付的手续费。在子合约中重写此方法,可自定义闪电贷手续费计算逻辑。同时也可以使得本ERC20具备通缩属性
    // - token: 闪电贷的token地址
    // - amount: 闪电贷的token数量
    function _flashFee(address token, uint256 amount) internal view virtual returns (uint256) {
        // 在不添加字节码的情况下,使得未使用传入变量在编译时不再报warning
        token;
        amount;
        // 直接返回0。
        // 注:在子合约中重写此方法,可自定义闪电贷手续费计算逻辑
        return 0;
    }

foundry代码验证:

contract ERC20FlashMintTest is Test {
    MockERC20FlashMint private _testing = new MockERC20FlashMint("test name", "test symbol", address(this), 10000);

    function test_FlashFee() external {
        // case 1: default flash fee (0)
        uint amountToLoan = 100;
        assertEq(_testing.flashFee(address(_testing), amountToLoan), 0);
        // revert with wrong token address
        vm.expectRevert("ERC20FlashMint: wrong token");
        _testing.flashFee(address(0), amountToLoan);

        // case 2: customized flash fee (10% amountToLoan)
        _testing.customizedFlashFeeAndReceiver();
        assertEq(_testing.flashFee(address(_testing), amountToLoan), amountToLoan / 10);
        // revert with wrong token address
        vm.expectRevert("ERC20FlashMint: wrong token");
        _testing.flashFee(address(0), amountToLoan);
    }
}
2.3 flashLoan(IERC3156FlashBorrower receiver, address token, uint256 amount, bytes calldata data)

执行闪电贷。如果成功执行,返回true。

本ERC20合约会mint出新的token给receiver地址。闪电贷结束时需要满足如下条件才算成功:

  1. receiver名下持有至少amount(借贷数量)+ fee(对应手续费)的token;
  2. receiver已授权给本ERC20合约至少如上数量的授权额度,以便本合约可以burn掉receiver名下的token及转移fee。
    // 参数:
    // - receiver: 闪电贷借出token的接受者地址,要求该地址实现了接口IERC3156FlashBorrower
    // - amount: 闪电贷借出token数量
    // - data: 传给receiver,用于执行receiver.onFlashLoan()方法的参数
    // 注:此方法未做重入检查,因为即使重入发生也不会带来风险——因为闪电贷mint出的token在后面都会被burn掉,一旦该平衡被打破整个函数会revert 
    function flashLoan(
        IERC3156FlashBorrower receiver,
        address token,
        uint256 amount,
        bytes calldata data
    ) public virtual override returns (bool) {
    	// 要求借贷token数量 <= 最大允许借贷数量,否则revert
        require(amount <= maxFlashLoan(token), "ERC20FlashMint: amount exceeds maxFlashLoan");
        // 计算对应闪电贷手续费
        uint256 fee = flashFee(token, amount);
        // mint给receiver amount数量的token
        _mint(address(receiver), amount);
        // 执行receiver.onFlashLoan()方法,来触发receiver收到贷款后的执行逻辑。要求返回值为常量_RETURN_VALUE,否则revert
        require(
            receiver.onFlashLoan(msg.sender, token, amount, fee, data) == _RETURN_VALUE,
            "ERC20FlashMint: invalid return value"
        );
        // 获取闪电贷手续费接受者地址
        address flashFeeReceiver = _flashFeeReceiver();
        // 消费receiver给本合约的授权额度,即amount(借贷token数量)+ fee(闪电贷手续费)
        _spendAllowance(address(receiver), address(this), amount + fee);
        if (fee == 0 || flashFeeReceiver == address(0)) {
        	// 如果手续费为0或者闪电贷手续费接受者地址为0地址,直接销毁该receiver名下amount+fee数量的token
            _burn(address(receiver), amount + fee);
        } else {
        	// 如果手续费不为0且闪电贷手续费接受者地址不为0地址
        	// 销毁receiver名下数量为amount的token
            _burn(address(receiver), amount);
            // 从receiver名下转移fee数量的手续费到手续费接受者地址
            _transfer(address(receiver), flashFeeReceiver, fee);
        }
        // 返回true
        return true;
    }
    
    // 返回闪电贷手续费的接受地址。如果该方法返回0地址,表示手续费被天然burn掉。如果需要换成其他地址,可以在子合约中重写该函数
    function _flashFeeReceiver() internal view virtual returns (address) {
        // 直接返回0地址
        return address(0);
    }

foundry代码验证:

contract ERC20FlashMintTest is Test {
    address private constant CUSTOMIZED_FLASH_FEE_RECEIVER = address(1024);

    MockERC20FlashMint private _testing = new MockERC20FlashMint("test name", "test symbol", address(this), 10000);
    ERC3156FlashBorrower private flashBorrower = new ERC3156FlashBorrower();

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

    event ParamsIn(
        address initiator,
        address token,
        uint256 amount,
        uint256 fee,
        bytes data
    );

    event Monitor(
        address owner,
        uint balance,
        uint totalSupply
    );

    function test_FlashLoan_DefaultFlashFeeAndReceiver() external {
        assertEq(_testing.totalSupply(), 10000);
        // case 1: pass with flash borrower's approval and valid return value
        uint amountToLoan = 20000;
        uint defaultFee = 0;
        flashBorrower.flipApprove();
        flashBorrower.flipValidReturnValue();

        // mint amountToLoan to flashBorrower
        vm.expectEmit(address(_testing));
        emit Transfer(address(0), address(flashBorrower), amountToLoan);
        // check params input in IERC3156FlashBorrower.onFlashLoan()
        vm.expectEmit(address(flashBorrower));
        emit ParamsIn(address(this), address(_testing), amountToLoan, defaultFee, '');
        // check the state during IERC3156FlashBorrower.onFlashLoan()
        vm.expectEmit(address(flashBorrower));
        emit Monitor(address(flashBorrower), amountToLoan, amountToLoan + 10000);
        // burn amountToLoan + fee(0) from flashBorrower
        vm.expectEmit(address(_testing));
        emit Transfer(address(flashBorrower), address(0), amountToLoan + defaultFee);
        _testing.flashLoan(flashBorrower, address(_testing), amountToLoan, '');
        // total supply not changed
        assertEq(_testing.totalSupply(), 10000);

        // case 2: revert if amountToLoan > maxFlashLoan
        uint amountExceedsMaxFlashLoan = _testing.maxFlashLoan(address(_testing)) + 1;
        vm.expectRevert("ERC20FlashMint: amount exceeds maxFlashLoan");
        _testing.flashLoan(flashBorrower, address(_testing), amountExceedsMaxFlashLoan, '');

        // case 3: revert if receiver.onFlashLoan() with invalid return value
        flashBorrower.flipValidReturnValue();
        vm.expectRevert("ERC20FlashMint: invalid return value");
        _testing.flashLoan(flashBorrower, address(_testing), amountToLoan, '');
        flashBorrower.flipValidReturnValue();

        // case 4: revert without approval in IERC3156FlashBorrower.onFlashLoan()
        flashBorrower.flipApprove();
        vm.expectRevert("ERC20: insufficient allowance");
        _testing.flashLoan(flashBorrower, address(_testing), amountToLoan, '');
        flashBorrower.flipApprove();

        // case 5: revert with different amounts can be minted and burned in onFlashLoan()
        // transfer 1 to address(1) in IERC3156FlashBorrower.onFlashLoan()
        bytes memory data = abi.encodeCall(_testing.transfer, (address(1), 1));
        vm.expectRevert("ERC20: burn amount exceeds balance");
        _testing.flashLoan(flashBorrower, address(_testing), amountToLoan, data);
    }

    function test_FlashLoan_CustomizedFlashFeeAndReceiver() external {
        _testing.customizedFlashFeeAndReceiver();
        assertEq(_testing.balanceOf(address(this)), 10000);
        assertEq(_testing.balanceOf(address(flashBorrower)), 0);
        assertEq(_testing.balanceOf(CUSTOMIZED_FLASH_FEE_RECEIVER), 0);

        // case 1: pass with flash borrower's approval and valid return value
        uint amountToLoan = 20000;
        uint customizedFlashFee = amountToLoan / 10;
        flashBorrower.flipApprove();
        flashBorrower.flipValidReturnValue();
        // transfer flash fee to flash borrower
        _testing.transfer(address(flashBorrower), customizedFlashFee);

        // mint amountToLoan to flashBorrower
        vm.expectEmit(address(_testing));
        emit Transfer(address(0), address(flashBorrower), amountToLoan);
        // check params input in IERC3156FlashBorrower.onFlashLoan()
        vm.expectEmit(address(flashBorrower));
        emit ParamsIn(address(this), address(_testing), amountToLoan, customizedFlashFee, '');
        // check the state during IERC3156FlashBorrower.onFlashLoan()
        vm.expectEmit(address(flashBorrower));
        emit Monitor(address(flashBorrower), amountToLoan + customizedFlashFee, amountToLoan + 10000);
        // burn amountToLoan from flashBorrower
        vm.expectEmit(address(_testing));
        emit Transfer(address(flashBorrower), address(0), amountToLoan);
        // transfer customizedFlashFee to customizedFlashFeeReceiver
        vm.expectEmit(address(_testing));
        emit Transfer(address(flashBorrower), CUSTOMIZED_FLASH_FEE_RECEIVER, customizedFlashFee);

        _testing.flashLoan(flashBorrower, address(_testing), amountToLoan, '');
        // total supply not changed
        assertEq(_testing.totalSupply(), 10000);
        assertEq(_testing.balanceOf(address(this)), 10000 - customizedFlashFee);
        assertEq(_testing.balanceOf(address(flashBorrower)), 0);
        assertEq(_testing.balanceOf(CUSTOMIZED_FLASH_FEE_RECEIVER), customizedFlashFee);

        // case 2: revert if amountToLoan > maxFlashLoan
        uint amountExceedsMaxFlashLoan = _testing.maxFlashLoan(address(_testing)) + 1;
        vm.expectRevert("ERC20FlashMint: amount exceeds maxFlashLoan");
        _testing.flashLoan(flashBorrower, address(_testing), amountExceedsMaxFlashLoan, '');

        // case 3: revert if receiver.onFlashLoan() with invalid return value
        flashBorrower.flipValidReturnValue();
        vm.expectRevert("ERC20FlashMint: invalid return value");
        _testing.flashLoan(flashBorrower, address(_testing), amountToLoan, '');
        flashBorrower.flipValidReturnValue();

        // case 4: revert without approval in IERC3156FlashBorrower.onFlashLoan()
        flashBorrower.flipApprove();
        vm.expectRevert("ERC20: insufficient allowance");
        _testing.flashLoan(flashBorrower, address(_testing), amountToLoan, '');
        flashBorrower.flipApprove();

        // case 5: revert with different amounts can be minted and burned in onFlashLoan()
        // transfer 1 to address(1) in IERC3156FlashBorrower.onFlashLoan()
        bytes memory data = abi.encodeCall(_testing.transfer, (address(1), 1));
        vm.expectRevert("ERC20: burn amount exceeds balance");
        _testing.flashLoan(flashBorrower, address(_testing), amountToLoan, data);

        // case 6: revert with insufficient flash fee
        _testing.transfer(address(flashBorrower), customizedFlashFee - 1);
        vm.expectRevert("ERC20: transfer amount exceeds balance");
        _testing.flashLoan(flashBorrower, address(_testing), amountToLoan, '');
    }
}

ps:
本人热爱图灵,热爱中本聪,热爱V神。
以下是我个人的公众号,如果有技术问题可以关注我的公众号来跟我交流。
同时我也会在这个公众号上每周更新我的原创文章,喜欢的小伙伴或者老伙计可以支持一下!
如果需要转发,麻烦注明作者。十分感谢!

在这里插入图片描述

公众号名称:后现代泼痞浪漫主义奠基人

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1337631.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue2中自定义右击菜单--vue-contextmenujs

第一步我们先安装这个依赖 npm install vue-contextmenujs 第二步把我们的依赖引入到全局中 import Contextmenu from vue-contextmenujs; Vue.use(Contextmenu) 第三步在我们需要右击菜单的代码上增加如下代码 第四步在方法中写入方法 当然其中还是有很多别的属性&#x…

Java web班级管理系统jsp【班级管理系统】(Java毕业设计)

大家好&#xff0c;我是DeBug&#xff0c;很高兴你能来阅读&#xff01;作为一名热爱编程的程序员&#xff0c;我希望通过这些教学笔记与大家分享我的编程经验和知识。在这里&#xff0c;我将会结合实际项目经验&#xff0c;分享编程技巧、最佳实践以及解决问题的方法。无论你是…

【华为机试】2023年真题B卷(python)-发广播

一、题目 题目描述&#xff1a; 某地有N个广播站&#xff0c;站点之间有些有连接&#xff0c;有些没有。有连接的站点在接受到广播后会互相发送。 给定一个N*N的二维数组matrix,数组的元素都是字符’0’或者’1’。 matrix[i][j]‘1’,则代表i和j站点之间有连接&#xff0c;mat…

项目零散记录

Ts托管 仅本项目禁用本地vscode内置的ts服务 提交代码前的检查 husky(哈士奇)工具&#xff08;是一个git hooks工具&#xff09; 1、安装 pnpm dlx husky-init && pnpm install安装的时候&#xff0c;出现如下报错 解决方案&#xff0c;需要先执行git init初始化…

【QML-布局】

QML编程指南 VX&#xff1a;hao541022348 ■ 布局■ Manual Positioning 手动定位■ Anchors 锚定位■ Positioners 定位器■ Row&#xff08;行定位器&#xff09;■ Column &#xff08;列定位器&#xff09;■ Grid&#xff08;表格定位器&#xff09;■ Flow&#xff08;流…

打印各种形状

package org.example;public class Demo {public static void main(String[] args) {//打印长方形printDemo1();//打印有1定空隙的长方形printDemo2();//平行四边形printDemo3();//三角形printDemo4();//菱形printDemo5();//空心菱形printDemo6();}private static void printDe…

【JVM】垃圾回收理论

一、关于回收目标 在前面我们已经了解到&#xff0c;JVM的内存模型划分为多个区域&#xff0c;由于不同区域的实现机制以及功能不同&#xff0c;那么各自的回收目标也不同。一般来说&#xff0c;内存回收主要涉及以下三个区域&#xff1a; 虚拟机栈/本地方法栈&#xff1a;顾名…

Markdown之高频语法介绍(二十四)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

Ionic实战二十七:移动端录音方案及Nginx部署配置

文章目录 1.最终效果预览2.实现思路说明3.移动端外壳集成iframe4.视频页nginx配置5.组态页iframe集成6.组态页Nginx配置7.Nginx启动及关闭8.H5页面录音1.最终效果预览 2.实现思路说明 摄像头对接的海康的或者大华,将设备集成到青柿视频平台中,这样视频的播放用video标签即可…

promise的使用和实例方法

前言 异步,是任何编程都无法回避的话题。在promise出现之前,js中也有处理异步的方案,不过还没有专门的api能去处理链式的异步操作。所以,当大量的异步任务逐个执行,就变成了传说中的回调地狱。 function asyncFn(fn1, fn2, fn3) {setTimeout(() > {//处理第一个异步任务fn1…

12.26_黑马数据结构与算法笔记Java

目录 243 图 Floyd Warshall 算法实现2 244 图 Floyd Warshall 算法实现3 245 图 Floyd Warshall 算法实现4 246 图 最小生成树 Prim 247 图 最小生成树 Kruskal 248 图 并查集 1 249 图 并查集 2 250 图 并查集 路径压缩 251 图 并查集 UnionBySize 252 贪心算法 介绍…

vue3开发一个todo List

创建新的 Vue 3 项目&#xff1a; 按装vue3的 工具 npm install -g vue/cli创建一个新的 Vue 3 项目&#xff1a; vue create vue3-todolist进入项目目录&#xff1a; cd vue3-todolist代码&#xff1a; 在项目的 src/components 目录下&#xff0c;创建一个新的文件 Todo…

蓝桥杯c/c++程序设计——冶炼金属

冶炼金属 问题描述 小蓝有一个神奇的炉子用于将普通金属 O 冶炼成为一种特殊金属 X。这个炉子有一个称作转换率的属性 V&#xff0c;V 是一个正整数&#xff0c;这意味着消耗 V 个普通金属 O 恰好可以冶炼出一个特殊金属 X&#xff0c;当普通金属 O 的数目不足 V 时&#xff0…

基于yolov8,制作停车位计数器(附源码)

大家好&#xff0c;YOLO(You Only Look Once) 是由Joseph Redmon和Ali开发的一种对象检测和图像分割模型。 YOLO的第一个版本于2015年发布&#xff0c;由于其高速度和准确性&#xff0c;瞬间得到了广大AI爱好者的喜爱。 Ultralytics YOLOv8则是一款前沿、最先进(SOTA)的模型&a…

随记-语义分割

Semantic Segmentation 什么是语义分割全卷积网络FCN摘要 什么是语义分割 语义分割 Semantic Segmentation 旨在对图像的每个像素进行分类&#xff0c;将其分配给预定义的语义类别。 &#xff08;检测图像中的物体并按属性分类&#xff09; 实例分割 Instance Segmentation 实…

Vue框架引入Element-Ui

首先已经创建好了 Vue 框架&#xff0c;安装好了 node.js。 没有完成的可按照此博客搭建&#xff1a;搭建Vue项目 之后打开终端&#xff0c;使用命令。 1、命令引入 npm i element-ui -S2、package.json 查看版本 在 package.json 文件里可查看下载好的依赖版本。 3、在 ma…

按照不同产品类型,划片机主要可以分为如下几个类别

随着科技的不断发展&#xff0c;划片机在半导体封装行业中的应用越来越广泛。根据不同的产品类型&#xff0c;划片机主要可以分为砂轮划片机和激光划片机两个类别。本文将详细介绍这两类划片机的特点和应用。 一、砂轮划片机 砂轮划片机是综合了水气电、空气静压高速主轴、精密…

【Vulnhub 靶场】【Funbox: Scriptkiddie】【非常简单】【20210720】

1、环境介绍 靶场介绍&#xff1a;https://www.vulnhub.com/entry/funbox-scriptkiddie,725/ 靶场下载&#xff1a;https://download.vulnhub.com/funbox/Funbox11.ova 靶场难度&#xff1a;简单 发布日期&#xff1a;2021年07月20日 文件大小&#xff1a;1.3 GB 靶场作者&…

Windows无法安装edge 无法连接Internet

如果出现以上问题&#xff0c;或者Edge浏览器无法更新&#xff0c;提示防火墙错误之类的都可以解决问题。 下载以下证书文件并导入即可解决问题。 MicrosoftRootCertificateAuthority2011.cer

《PCI Express体系结构导读》随记 —— 第I篇 第1章 PCI总线的基本知识(4)

接前一篇文章&#xff1a;《PCI Express体系结构导读》随记 —— 第I篇 第1章 PCI总线的基本知识&#xff08;3&#xff09; 1.1 PCI总线的组成 PCI总线作为处理器系统的本地总线&#xff0c;是处理器系统的一个组成部件。因此&#xff0c;讲述PCI总线的组成结构&#xff0c;不…