ERC-6909 最小多代币标准

news2025/3/18 19:06:54

ERC-6909 Token标准是 ERC-1155 Token标准的一种简化替代方案。

ERC-1155 标准引入了一种多Token接口,使得单个智能合约能够结合可替代的和不可替代的Token(即,​ERC20 和 ERC721)。

ERC-1155 解决了多个挑战,例如降低部署成本、最小化以太坊区块链上的冗余字节码,以及简化多Token交易的Token批准流程。

然而,由于每次转账都强制要求回调、强制包含批量转账,以及缺乏对单操作员批准方案的细粒度控制,它引入了一些膨胀和气体低效问题。ERC-6909 通过消除合约级回调和批量转账,并用混合(限额-操作员)权限方案替代单操作员的凭证方案,从而解决了这些缺点,以实现颗粒化的Token管理。

注意: 以下部分假设读者对 ERC-1155 标准及其概念有一定的了解。如果你不熟悉,请在继续之前阅读相关内容。

ERC-6909 和 ERC-1155 标准的对比

ERC-6909 移除转账的回调要求

ERC-1155 规范要求 safeTransferFrom 和 safeBatchTransferFrom 检查接收账户是否为合约。如果是,则必须在接收合约账户上调用 ERC1155TokenReceiver 接口函数(onERC1155ReceivedonERC1155BatchReceived)以检查其是否接受转账。

这些回调在某些情况下是有用的。然而,对于希望不使用这种行为的接收方,它们是不必要的外部调用。回调影响接收合约账户的Gas成本和代码大小,因为它们需要实现多个回调(即,通过 onERC1155ReceivedonERC1155BatchReceived)并返回魔术的 4 字节值以接收Token。相比之下,ERC-6909 的实现者可以自定义他们的回调架构。

ERC-6909 省略了批量转账逻辑

尽管批量转账有时是有益的,但在 ERC-6909 标准中故意省略,以允许开发者根据特定执行环境实施批量转账逻辑。开发者可以根据自己的需要实现批量转账,而无需仅仅为了遵循标准而添加额外的批量转账函数。

下面展示的 safeBatchTransferFrom 函数在 ERC-1155 标准中执行批量转账。然而,其强制的包含为不需要它们的应用程序增加了膨胀:

// ERC-1155
function safeBatchTransferFrom(
    address _from,
    address _to,
    uint256[] calldata _ids,
    uint256[] calldata _values,
    bytes calldata _data
) external;

​以下是 ERC-6909 transferFrom 函数。我们可以看到批量特性和 _data 参数已被删除。

// ERC-6909
function transferFrom(
    address sender,
    address receiver,
    uint256 id,
    uint256 amount
) public returns (bool) {
    if (sender != msg.sender && !isOperator[sender][msg.sender]) {
        uint256 senderAllowance = allowance[sender][msg.sender][id];
        if (senderAllowance < amount) revert InsufficientPermission();
        if (senderAllowance != type(uint256).max) {
            allowance[sender][msg.sender][id] = senderAllowance - amount;
        }
    }
    if (balanceOf[sender][id] < amount) revert InsufficientBalance();
    balanceOf[sender][id] -= amount;
    balanceOf[receiver][id] += amount;
    emit Transfer(msg.sender, sender, receiver, id, amount);
    return true;
}

ERC-6909 同时支持全局批准和细粒度限额

// 在 ERC-1155 →
function setApprovalForAll(
    address _operator,
    bool _approved
) external;

上面展示的 setApprovalForAll 函数是 ERC-1155 中的全局操作员模型,允许一个账户授权另一个账户管理(作为操作员)其所有Token ID 的操作。一旦被授权,操作员可以随意转移授权账户拥有的任何数量的任何Token ID。

虽然这种方法简化了委托,但缺乏细粒度控制:

  • 没有办法授予特定于单个Token ID 或数量的权限。
  • 这种全有或全无的方法不适合需要受控权限的场景。

为引入细粒度控制,ERC-6909 混合操作员权限方案包含以下内容:

  • 来自 ERC-1155 的操作员模型,
  • 和受 ERC-20 启发的限额模型。

ERC-6909 中的操作员模型

在下面的 ERC-6909 setOperator 函数中,spender 变量被设置为操作员,并被授权无条件的权限,以转移账户所拥有的所有Token ID 而没有限额限制。

function setOperator(address spender, bool approved) public returns (bool) {
    isOperator[msg.sender][spender] = approved;
    emit OperatorSet(msg.sender, spender, approved);
   return true;
}

ERC-6909 中的限额模型

限额模型引入了一种特定于Token和数量的控制系统,其中一个账户可以为特定Token ID 设置有限的限额。

例如,Alice 可以允许 Bob 转移 100 个 ID 为 42 的Token,而不授予对其他Token ID 或不受限制的数量的访问权限,使用下一个展示的 ERC-6909 中的 approve 函数。

function approve(address spender, uint256 id, uint256 amount) public returns (bool) {
    allowance[msg.sender][spender][id] = amount;
    emit Approval(msg.sender, spender, id, amount);
    return true;
}

在 approve 中的 spender 变量是被授权代表Token所有者转移特定金额的特定Token ID 的账户。

例如,Token所有者可以允许 spender 转移 <= 100 个特定Token ID。或者,他们还可以通过将限额设置为 type(uint256).max 来为特定Token ID 授予无限制的批准。

ERC-6909 并没有指定是否应扣减设置为 type(uint256).max 的限额。相反,这种行为留给实现者的自由裁量权,类似于 ERC-20。

核心数据结构

ERC-6909 实现使用三个映射来更新账户余额和批准状态。

balanceOf:ID 的所有者余额

balanceOf 映射跟踪特定Token ID 由地址 (owner) 持有的余额。映射中的 owner => (id => amount) 结构表示单个所有者可以持有多个Token,并通过各自的 ID 跟踪其余额。

mapping(address owner => mapping(uint256 id => uint256 amount)) public balanceOf;

allowance:ID 的支出者限额

允许映射定义支出者在所有者的授权下可以转移多少特定Token(ID)。它促进了对Token支出的细粒度控制。

mapping(address owner => mapping(address spender => mapping(uint256 id => uint256 amount))) public allowance;

例如,allowance[0xDEF...][0x123...][5] 将返回所有者 0xDEF... 允许支出者 0x123... 转移的Token(ID 为 5)的数量。

isOperator:操作员批准状态

mapping(address owner => mapping(address operator => bool isOperator)) public isOperator;

该映射跟踪支出者是否被批准作为拥有地址的所有Token的操作员。例如,isOperator[0x123...][0xABC...] 返回 true 如果地址 0xABC... 被允许支出地址 0x123... 所拥有的Token;否则返回 false

核心 ERC-6909 功能及其数据参数

转账函数

该规范并未遵循 ERC-721 和 ERC-1155 中的“安全转账机制”,因为由于其向任意合约的外部调用被认为是误导性的。ERC-6909 使用 transfer 和 transferFrom 函数,详情如下。

转账:

ERC-6909 的 transfer 函数表现得与 ERC-20 的 transfer 相同,只不过它适用于特定的Token ID。该函数接受接收地址、Token的 ID 和要转移的金额作为输入参数,并使用 balanceOf 映射更新余额。与 ERC-20 转账函数类似,成功执行事务时返回 true 是必要的。

// ERC-20 接口转账函数

function transfer(address _to, uint256 _value) public returns (bool)

    // ERC-6909 转账函数参考实现
    // @notice 将数量 id 从调用者转移至接收者。
    // @param receiver 接收者的地址。
    // @param id Token的 ID。
    // @param amount Token的数量。

    function transfer(address receiver, uint256 id, uint256 amount) public returns (bool) {
        if (balanceOf[msg.sender][id] < amount) revert InsufficientBalance(msg.sender, id);
        balanceOf[msg.sender][id] -= amount;
        balanceOf[receiver][id] += amount;
        emit Transfer(msg.sender, msg.sender, receiver, id, amount);
        return true;
    }

transferFrom:

ERC-6909 的 transferFrom 函数与 ERC-20 的不同之处在于它要求提供一个Token ID。此外,它还检查操作员的批准以及限额。

该函数首先检查 if (sender != msg.sender && !isOperator[sender][msg.sender]),确保调用者 (msg.sender) 是:

  • 所有者sender),或者
  • 已批准的操作员isOperator[sender][msg.sender] == true)。

如果 msg.sender 不是所有者也不是已批准的操作员,函数将检查调用者是否具有 足够的限额 以进行转移。如果有限额但未设置为无限制type(uint256).max),则从限额中扣除转移的 amount

此外,标准规定如果调用者是操作员或 sender,则该函数不应扣减调用者对于Token id 的 allowance 中的 amount

// ERC-6909 transferFrom

function transferFrom(address sender, address receiver, uint256 id, uint256 amount) public returns (bool) {

    if (sender != msg.sender && !isOperator[sender][msg.sender]) {
        uint256 senderAllowance = allowance[sender][msg.sender][id];
        if (senderAllowance < amount) revert InsufficientPermission();
        if (senderAllowance != type(uint256).max) {
            allowance[sender][msg.sender][id] = senderAllowance - amount;
        }
    }

    if (balanceOf[sender][id] < amount) revert InsufficientBalance();

    balanceOf[sender][id] -= amount;
    balanceOf[receiver][id] += amount;
    emit Transfer(msg.sender, sender, receiver, id, amount);
    return true;
}

approve:

approve 函数允许调用者(msg.sender)向支出者授予特定Token(ID)的特定限额。这会更新限额映射以反映新的限额并发出 Approval 事件。

function approve(address spender, uint256 id, uint256 amount) public returns (bool) {
    allowance[msg.sender][spender][id] = amount;
    emit Approval(msg.sender, spender, id, amount);
    return true;
}

setOperator:

setOperator 函数允许调用者(msg.sender)通过将批准参数设置为 true 或 false 来授予或撤销特定地址(spender)的操作员权限。该函数会相应地更新 isOperator 映射,并发出 OperatorSet 事件,以通知外部监听器有关更改的情况。

function setOperator(address spender, bool approved) public returns (bool) {
    isOperator[msg.sender][spender] = approved;
    emit OperatorSet(msg.sender, spender, approved);
    return true;
}

ERC-6909 中的事件与日志

ERC-6909 定义了关键事件,以跟踪多Token合约中的Token转移、批准及操作员权限。

1. 转移事件:

/// @notice 转移发生时发出的事件。

event Transfer(address caller, address indexed sender, address indexed receiver, uint256 indexed id, uint256 amount);

ERC-6909 中的 Transfer 事件用于跟踪Token的移动,必须在以下条件下发出:

  • 转移Token id 的 amount 从一个账户到另一个账户时,将记录 senderreceivertoken ID 和转移的 amount
  • 当创建新Token时,事件必须以 sender 为零地址(0x0)发出。
  • 当Token被销毁时,事件必须以接收方为零地址(0x0)发出,以表示Token被移除。

2. OperatorSet 事件:

/// @notice 操作员被设置时发出的事件。
event OperatorSet(address indexed owner, address indexed spender, bool approved);

OperatorSet 事件每当所有者分配或撤销另一个地址的操作员权限时都会发出。事件记录所有者的地址、支出者的地址以及更新的批准状态(true 表示授予,false 表示撤销)。

3. Approval 事件:

/// @notice 批准发生时发出的事件。
event Approval(address indexed owner, address indexed spender, uint256 indexed id, uint256 amount);

当所有者设置或更新支出者转移特定金额的特定Token ID 的批准时,必须发出 Approval 事件。事件记录 ownerspender、Token id 和批准的 amount

现在我们已经探讨了 ERC-6909 与 ERC-1155 之间的差异,以及 ERC-6909 中的核心方法和事件,让我们看看标准的一些实际应用。

Uniswap v4 PoolManager 怎样实现 ERC-6909。

在 Uniswap v3 中,工厂/池模型通过使用 UniswapV3Factory 合约为每个池部署一个单独的合约来创建新的Token对。这种方法增加了Gas成本,因为每个新池都需要新的合约部署。

与此相比,Uniswap v4 引入了一个单例合约(PoolManager.sol),该合约将所有流动性池管理作为其内部状态的一部分,而不是要求单独的合约部署。这一设计显著降低了池创建的Gas成本。

此外,以前版本中涉及 多个 Uniswap 池 的交易需要跨多个合约的Token转移和冗余状态更新。在 Uniswap v4 中,PoolManager 合约可以集中持有用户的 ERC-6909 表示的 ERC-20 Token,而不需要在池中往返转移 ERC-20 Token。

例如,如果用户为Token A 提供流动性,他们后来可以选择提取其股份并接收Token A 作为转移到其钱包的 ERC-20。然而,如果他们选择不提取Token,那么 Uniswap v4 的 PoolManager 可以 铸造其Token余额的 ERC-6909 表示,而无需从合约转移 ERC-20 Token——节省了跨合约调用。这些 ERC-6909 余额允许用户在协议中交易或互动,而无需在钱包之间移动Token。

这意味着当用户后来将Token A 兑换为Token B 时,Uniswap 只是更新他们在池中的 ERC-6909 余额

注意:ERC-6909 在 Uniswap v4 中不作为 LP 代币使用。

ERC-6909 元数据在单例 DeFi 架构和 NFT 系列中的考虑

以下是 IERC6909Metadata 接口,它定义了如何将 ERC-6909 标准和单独Token相关联的元数据,例如名称、符号和小数,其函数可以根据 id 的不同发展,允许 ERC-6909 中不同的Token具有不同的名称、符号和小数。

/// @notice 包含单个Token元数据的合同。
interface IERC6909Metadata is IERC6909 {
    /// @notice 给定Token的名称。
    /// @param id Token的 id。
    /// @return name Token的名字。
    function name(uint256 id) external view returns (string memory);

    /// @notice 给定Token的 символ。
    /// @param id Token的 id。
    /// @return symbol Token的符号。
    function symbol(uint256 id) external view returns (string memory);

    /// @notice 给定Token的小数位数。
    /// @param id Token的 id。
    /// @return decimals Token的小数位数。
    function decimals(uint256 id) external view returns (uint8);
}

对于 DeFi 协议,我们可能有多个 LP 代币,并且我们可能希望将其标准化为都具有相同的小数,例如 18。然而,我们可能希望名称和符号能够反映池中持有的不同资产。

相比之下,对于 NFT,decimals 值应始终设置为 1,因为 NFT 是不可分割的。

在典型的 NFT 系列(例如 ERC-721)中,所有Token共享相同的名字和符号,以表示整个系列(例如 "CryptoPunks" 和符号 "PUNK")。ERC-6909 使我们能够遵循 ERC-712 规范,其中所有 NFT 在同一系列中共享相同的元数据。

ERC-6909 非同质化Token的实现示例。

ERC-6909 规范并没有明确规定支持非同质化Token的独特方法。但是,可以使用 ERC-1155 规范中描述的 ID 位拆分技术在 ERC-6909 中实现非同质化Token。这种方法使用 位移和加法运算 将集合 ID 和项目 ID 编码在一个 uint256 Token ID 中。

function getTokenId(uint256 collectionId, uint256 itemId) public pure returns (uint256) {
    return (collectionId << 128) + itemId;
}

下面的 ERC6909MultiCollectionNFT 合约是一个使用 getTokenId 根据 collectionId 和 itemId 生成Token ID 的非同质化Token(NFT)实现示例。

mintNFT 函数确保每个 tokenId 只能被铸造一次,无论地址如何。它使用 mintedTokens 映射跟踪 NFT tokenId 是否已被全球铸造。

由于在 mintNFT 中将 amount 变量设置为 1,因此函数中的 _mint(to, tokenId, amount) 调用将对 tokenId 铸造一份。在任何情况下,如果 amount > 1,Token将变为可替代,而不是非同质化。

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

import "./ERC6909.sol";

contract ERC6909MultiCollectionNFT is ERC6909 {
    struct NFT {
        string uri;
    }

    mapping(uint256 => NFT) private _tokens;
    mapping(uint256 => string) private _collectionURIs;
    mapping(uint256 => bool) public mintedTokens;

    event MintedNFT(address indexed to, uint256 indexed collectionId, uint256 indexed itemId, uint256 tokenId, string uri);

    // 通过连接 collectionId 和 itemId 计算 Token ID
    function getTokenId(uint256 collectionId, uint256 itemId) public pure returns (uint256) {
        return (collectionId << 128) + itemId;
    }

    function _mint(address to, uint256 tokenId, uint256 amount) internal {
        balanceOf[to][tokenId] += amount;
        emit Transfer(msg.sender, address(0), to, tokenId, amount);
    }

    function mintNFT(address to, uint256 collectionId, uint256 itemId, string memory uri) external {
        uint256 amount = 1;
        uint256 tokenId = getTokenId(collectionId, itemId);

        require(!mintedTokens[tokenId], "ERC6909MultiCollectionNFT: Token already minted");
        require(amount == 1, "ERC6909MultiCollectionNFT: Token copies must be 1");

        _tokens[tokenId] = NFT(uri);

        mintedTokens[tokenId] = true; // 标记为已铸造
        _mint(to, tokenId, amount); // amount 被定义为 1。

        emit MintedNFT(to, collectionId, itemId, tokenId, uri);
    }

    function nftBalanceOf(address owner, uint256 tokenId) public view returns (uint256) {
        return balanceOf[owner][tokenId];

    }
}

请记住,上面 mintNFT 中的 _mint 调用将余额映射更新为 1,因为这些铸造的Token完全是非同质化的。因此,在此合约中,如果 owner 地址确实铸造了 tokenIdnftBalanceOf 函数预计总是返回 1。

为了转移Token的所有权,下面的 nftTransfer 函数确保只有 NFT 所有者可以通过验证其余额启动转移,才能允许唯一存在的单位转移。

function nftTransfer(address to, uint256 tokenId) external {
    require(balanceOf[tokenId][msg.sender] == 1, "ERC6909MultiCollectionNFT: This should be non-fungible.");
    require(to != address(0), "ERC6909MultiCollectionNFT: transfer to zero address");

    transfer(to, tokenId, 1);
    // 在此情况下,数量等于 1。

    emit Transfer(msg.sender, address(0), to, tokenId, 1);
}

ERC-6909 内容 URI 扩展与元数据 URI JSON 模式

为了标准化 ERC-6909 中的元数据访问,可选的 IERC6909ContentURI 接口为检索合约和Token层次的元数据定义了两个 URI 函数(contractURI 和 tokenURI)。ERC-6909 标准并不强制Token需要关联 URI 元数据。然而,如果实现中包含这些 URI 函数,返回的 URI 应该指向遵循 ERC-6909 元数据 URI JSON 架构的 JSON 文件。

pragma solidity ^0.8.19;

import "./IERC6909.sol";

/// @title ERC6909 内容 URI 接口
interface IERC6909ContentURI is IERC6909 {
    /// @notice 合同级别 URI
    /// @return uri 合同级别的 URI。
    function contractURI() external view returns (string memory);

    /// @notice Token级别 URI
    /// @param id Token的 ID。
    /// @return uri Token级别的 URI。
    function tokenURI(uint256 id) external view returns (string memory);
}

如上所示,ERC-6909 IERC6909ContentURI 接口定义了两个可选的 URI 函数,即 contractURI 和 tokenURI;每个函数都有其相应的 URI JSON 模式。contractURI 函数(不带参数)返回指向合约级别元数据的单个 URI,而 tokenURI() 返回每个Token ID 特定的 URI。

以下是根据 ERC-6909 标准构造合约 URI JSON 架构的示例。

{
  "title": "Contract Metadata",
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "description": "The name of the contract."
    },
    "description": {
      "type": "string",
      "description": "The description of the contract."
    },
    "image_url": {
      "type": "string",
      "format": "uri",
      "description": "The URL of the image representing the contract."
    },
    "banner_image_url": {
      "type": "string",
      "format": "uri",
      "description": "The URL of the banner image of the contract."
    },
    "external_link": {
      "type": "string",
      "format": "uri",
      "description": "The external link of the contract."
    },
    "editors": {
      "type": "array",
      "items": {
        "type": "string",
        "description": "An Ethereum address representing an authorized editor of the contract."
      },
      "description": "An array of Ethereum addresses representing editors (authorized editors) of the contract."
    },
    "animation_url": {
      "type": "string",
      "description": "An animation URL for the contract."
    }
  },
  "required": ["name"]
}

tokenURI 函数则接受一个 uint256 参数 id,并返回该Token的 URI。如果Token id 不存在,该函数可以回滚。与合约级别的 URI 一样,客户在与合约交互时 必须 替换 URI 中每处 {id} 的出现为实际的Token ID,以访问与该Token相关的正确元数据。

以下是 tokenURI 函数的实现,返回遵循占位符格式的 静态 URI 模板

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

import "./ERC6909.sol";
import "./interfaces/IERC6909ContentURI.sol";

contract ERC6909ContentURI is ERC6909, IERC6909ContentURI {
    /// @notice 合同级 URI。
    string public contractURI;

    /// @notice 每个 id 的 URI。
    /// @return Token的 URI。
    function tokenURI(uint256) public pure override returns (string memory) {
        return "<baseuri>/{id}";
    }
}

以下是根据 ERC-6909 标准构造 URI JSON 架构的示例。

{
  "title": "Asset Metadata",
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "description": "Identifies the token"
    },
    "description": {
      "type": "string",
      "description": "Describes the token"
    },
    "image": {
      "type": "string",
      "description": "A URI pointing to an image resource."
    },
    "animation_url": {
      "type": "string",
      "description": "An animation URL for the token."
    }
  },
  "required": ["name", "description", "image"]
}

ERC-6909 规范中的限额和操作员歧义。

考虑一个场景,其中一个账户(A)授予另一个账户(B)操作员权限,并为 B 设置可转移特定金额的Token的限额。

如果 B 代表 A 发起转移,则实现必须确定检查的正确顺序以及限额如何与操作员权限互动。

歧义涉及检查的顺序。合约应该:

  1. 首先检查限额,如果不足则回滚,即使 B 具有操作员权限。
  2. 首先检查操作员权限,无论限额如何都允许转移。

在下面的 allowanceFirst 合约中,如果账户 B 具有操作员权限,但限额不足,限额检查将失败,从而导致事务回滚。这可能是反直观的,因为操作员权限通常意味着无限制访问,用户可能会期望事务成功。

相反,在 operatorFirst 合约中,如果实现首先检查操作员权限,将绕过限额检查,事务将基于操作员的无限制访问而成功。

contract operatorFirst {

function transferFrom(address sender, address receiver, uint256 id, uint256 amount) public {
    // 首先检查 `isOperator`
    if (msg.sender != sender && !isOperator[sender][msg.sender]) {
        require(allowance[sender][msg.sender][id] >= amount, "insufficient allowance");
        allowance[sender][msg.sender][id] -= amount;
    }

    // -- 剪切 --
    }
}

contract allowanceFirst{

    function transferFrom(address sender, address receiver, uint256 id, uint256 amount) public {
     // 首先检查限额是否充足
    if (msg.sender != sender && allowance[sender][msg.sender][id] < amount) {
        require(isOperator[sender][msg.sender], "insufficient allowance");
    }

    // 错误:当限额不足时,由于算术下溢而发生恐慌,无论调用者是否具有操作员权限。
    allowance[sender][msg.sender][id] -= amount;

    // -- 剪切 --
 }
}

该标准故意将权限检查的决定留给实现者,这给实现者提供灵活性。在一个账户同时拥有操作员权限和不足的限额时,转账行为取决于检查的顺序。

结论

ERC-6909 标准通过移除转账函数中的批量和强制回调,显著提高了 ERC-1155 的效率。去除批量处理允许逐个案例优化,特别是对于汇总或气体敏感的环境。

它还通过混合操作员权限方案引入了可扩展的Token批准控制,更多信息,,https://t.me/+_QibemQqIIg1OTY1。

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

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

相关文章

各省水资源平台 水资源遥测终端机都用什么协议

各个省水资源平台 水资源遥测终端机 的建设大部分从2012年开始启动&#xff0c;经过多年建设&#xff0c;基本都已经形成了稳定的通讯要求&#xff1b;河北瑾航科技 遥测终端机&#xff0c;兼容了大部分省市的通讯协议&#xff0c;如果需要&#xff0c;可以咨询和互相学习&…

需求分析、定义、验证、变更、跟踪(高软47)

系列文章目录 需求分析、定义、验证、变更、跟踪 文章目录 系列文章目录前言一、需求分析二、需求定义三、需求验证四、需求变更五、需求跟踪六、真题总结 前言 本节讲明需求分析、定义、验证、变更、跟踪相关知识。 一、需求分析 二、需求定义 三、需求验证 四、需求变更 五、…

从零开始 | C语言基础刷题DAY3

❤个人主页&#xff1a;折枝寄北的博客 目录 1.打印3的倍数的数2.从大到小输出3. 打印素数4.打印闰年5.最大公约数 1.打印3的倍数的数 题目&#xff1a; 写一个代码打印1-100之间所有3的倍数的数字 代码&#xff1a; int main(){int i 0;for (i 1; i < 100; i){if (i % …

docker入门篇

使用docker可以很快部署相同的环境,这也是最快的环境构建,接下来就主要对docker中的基础内容进行讲解.Docker 是一个用于开发、交付和运行应用程序的开源平台&#xff0c;它可以让开发者将应用程序及其依赖打包到一个容器中&#xff0c;然后在任何环境中运行这个容器&#xff0…

Unity Shader - UI Sprite Shader之简单抠图效果

Sprite抠图效果&#xff1a; 前言 在PhotoShop中我们经常会用到抠图操作&#xff0c;现在就用Shader实现一个简单的抠图效果。 实现原理&#xff1a; 使用当前像素颜色与需要抠掉的颜色相减作比较&#xff0c;然后与一个指定的阈值比较以决定是否将其显示出来&#xff1b; U…

vllm-openai多服务器集群部署AI模型

服务器配置是两台ubantu系统电脑,每台电脑安装两张4090-48G显存的显卡,共计192G显存。 服务器1 服务器2 准备工作: 1.两台电脑都已经安装了docker 2.两台电脑都已经安装了nvidia驱动 参考vllm官方资料 https://docs.vllm.ai/en/latest/serving/distributed_serving.html…

在Spring Boot项目中接入DeepSeek深度求索,感觉笨笨的呢

文章目录 引言1. 什么是DeepSeek&#xff1f;2. 准备工作2.1 注册DeepSeek账号 3.实战演示3.1 application增加DS配置3.2 编写service3.3 编写controller3.4 编写前端界面chat.html3.5 测试 总结 引言 在当今快速发展的数据驱动时代&#xff0c;企业越来越重视数据的价值。为了…

STM32---FreeRTOS事件标志组

一、简介 事件标志位&#xff1a;用一个位&#xff0c;来表示事件是否发生 事件标志组&#xff1a;一组事件标志位的集合&#xff0c;可以简单的理解时间标志组&#xff0c;就是一个整体。 事件标志租的特点&#xff1a; 它的每一个位表示一个时间&#xff08;高8位不算&…

Word 小黑第40套

对应大猫43 主题 -浏览主题 -选择W样式标准文件就行 1级段落和2级段落&#xff08;用项目符号不影响原本段落文字符号 颜色修改为自动&#xff09; 整段变红的 不是把光标定位到红色字体那里 要选择几个红色字体 再创建样式 插入的空白页一定要是下一页&#xff0c;不能插空白…

【Linux我做主】浅谈Shell及其原理

浅谈Linux中的Shell及其原理 Linux中Shell的运行原理github地址前言一、Linux内核与Shell的关系1.1 操作系统核心1.2 用户与内核的隔离 二、Shell的演进与核心机制2.1 发展历程2.2 核心功能解析2.3 shell的工作流程1. 用户输入命令2. 解析器拆分指令3. 扩展器处理动态内容变量替…

数据结构篇——二叉树的存储与遍历

一、引入 书接上文&#xff0c;文于此续。上文我们学到了树的存储结构&#xff0c;那么今天&#xff0c;我们来学习下几种特殊的二叉树以及关于它的各种遍历&#xff0c;让我们一起加油吧。 二、特殊的二叉树 二叉树的特殊形式这里介绍3种&#xff0c;其中需要着重记忆的有…

分而治之:用于 RGB-T 显著目标检测的 Confluent Triple-Flow 网络(问题)

摘要 问题一&#xff1a;RGB-thermal显著对象检测这是什么&#xff1f; RGB图像是可见光的三通道图像&#xff0c;而thermal是热红外图像&#xff0c;通常为单通道&#xff0c;记录物体的热辐射信息。结合RGB和thermal两种模态的数据&#xff0c;可以利用两者的互补信息&…

求职招聘网站源码,找工作招工系统,支持H5和各种小程序

招聘找活招工平台系统源码 招聘求职找工作软件 发布信息积分充值招聘系统,里面带纤细教程 功能介绍: 招工小程序主要针对工地招工工人找工作,工地可以发布招工信息,工人可以发布找活信息,招工信息可以置顶,置顶需要积分,积分可以通过签到、分享邀请好友、充值获取,后…

企业微信群聊机器人开发

拿到机器人hook 机器人开发文档 https://developer.work.weixin.qq.com/document/path/91770

基于Python的tkinter开发的一个工具,解析图片文件名并将数据自动化导出为Excel文件

文章目录 一、开发背景与业务价值二、系统架构设计1. 分层架构图解2. 核心类结构3. 文件解析流程 三、关键技术实现详解1. 高性能文件名解析引擎2. 可视化数据展示3. 智能Excel导出模块 四、完整代码五、行业应用展望 一、开发背景与业务价值 在零售行业会员管理场景中&#x…

Flutter_学习记录_状态管理之GetX

1. 状态管理、Flutter Getx介绍 1.1 状态管理 通俗的讲&#xff1a;当我们想在多个页面&#xff08;组件/Widget&#xff09;之间共享状态&#xff08;数据&#xff09;&#xff0c;或者一个页面&#xff08;组件/Widget&#xff09;中的多个子组件之间共享状态&#xff08;数…

【网络】数据流(Data Workflow)Routes(路由)、Controllers(控制器)、Models(模型) 和 Middleware(中间件)

在图片中&#xff0c;数据流&#xff08;Data Workflow&#xff09;描述了应用程序中数据的流动过程&#xff0c;涉及 Routes&#xff08;路由&#xff09;、Controllers&#xff08;控制器&#xff09;、Models&#xff08;模型&#xff09; 和 Middleware&#xff08;中间件&…

Git下载安装(保姆教程)

目录 1、Git下载 2、Git安装&#xff08;windows版&#xff09; &#xff08;1&#xff09;启动安装程序 &#xff08;2&#xff09;阅读许可协议 &#xff08;3&#xff09;选择安装路径 &#xff08;4&#xff09;选择组件 &#xff08;5&#xff09;选择开始菜单文件夹…

Blender-MCP服务源码2-依赖分析

Blender-MCP服务源码2-依赖分析 有个大佬做了一个Blender-MCP源码&#xff0c;第一次提交代码是【2025年3月7号】今天是【2025年月15日】也就是刚过去一周的时间&#xff0c;所以想从0开始学习这个代码&#xff0c;了解一下大佬们的开发思路 1-核心知识点 from mcp.server.fas…

LabVIEW压比调节器动态试验台

本案介绍了一种基于LabVIEW的压比调节器动态试验台的设计&#xff0c;通过实用的LabVIEW图形化编程语言&#xff0c;优化了数据采集与处理的整个流程。案例通过实际应用展示了设计的专业性与高效性&#xff0c;以及如何通过系统化的方法实现精确的动态测试和结果分析。 ​ 项目…