【从0学习Solidity】51. ERC4626 代币化金库标准

news2024/11/19 23:28:18

【从0学习Solidity】51. ERC4626 代币化金库标准

在这里插入图片描述

  • 博主简介:不写代码没饭吃,一名全栈领域的创作者,专注于研究互联网产品的解决方案和技术。熟悉云原生、微服务架构,分享一些项目实战经验以及前沿技术的见解。
  • 关注我们的主页,探索全栈开发,期待与您一起在移动开发的世界中,不断进步和创造!
  • 本文收录于 不写代码没饭吃 的学习汇报系列,大家有兴趣的可以看一看。
  • 欢迎访问我们的微信公众号:不写代码没饭吃,获取更多精彩内容、实用技巧、行业资讯等。您关注的是我们前进的动力!

我们经常说 DeFi 是货币乐高,可以通过组合多个协议来创造新的协议;但由于 DeFi 缺乏标准,严重影响了它的可组合性。而 ERC4626 扩展了 ERC20 代币标准,旨在推动收益金库的标准化。这一讲,我们将介绍 DeFi 新一代标准 ERC4626,并写一个简单的金库合约。教学代码参考 openzeppelin 和 solmate 中的 ERC4626 合约,仅用作教学。

金库

金库合约是 DeFi 乐高中的基础,它允许你把基础资产(代币)质押到合约中,换取一定收益,包括以下应用场景:

  • 收益农场: 在 Yearn Finance 中,你可以质押 USDT 获取利息。
  • 借贷: 在 AAVE 中,你可以出借 ETH 获取存款利息和贷款。
  • 质押: 在 Lido 中,你可以质押 ETH 参与 ETH 2.0 质押,得到可以升息的 stETH

ERC4626

51-1.png

由于金库合约缺乏标准,写法五花八门,一个收益聚合器需要写很多接口对接不同的 DeFi 项目。ERC4626 代币化金库标准(Tokenized Vault Standard)横空出世,使得 DeFi 能够轻松扩展。它具有以下优点:

  1. 代币化: ERC4626 继承了 ERC20,向金库存款时,将得到同样符合 ERC20 标准的金库份额,比如质押 ETH,自动获得 stETH。

  2. 更好的流通性: 由于代币化,你可以在不取回基础资产的情况下,利用金库份额做其他事情。拿 Lido 的 stETH 为例,你可以用它在 Uniswap 上提供流动性或交易,而不需要取出其中的 ETH。

  3. 更好的可组合性: 有了标准之后,用一套接口可以和所有 ERC4626 金库交互,让基于金库的应用、插件、工具开发更容易。

总而言之,ERC4626 对于 DeFi 的重要性不亚于 ERC721 对于 NFT 的重要性。

ERC4626 要点

ERC4626 标准主要实现了一下几个逻辑:

  1. ERC20: ERC4626 继承了 ERC20,金库份额就是用 ERC20 代币代表的:用户将特定的 ERC20 基础资产(比如 WETH)存进金库,合约会给他铸造特定数量的金库份额代币;当用户从金库中提取基础资产时,会销毁相应数量的金库份额代币。asset() 函数会返回金库的基础资产的代币地址。
  2. 存款逻辑:让用户存入基础资产,并铸造相应数量的金库份额。相关函数为 deposit()mint()deposit(uint assets, address receiver) 函数让用户存入 assets 单位的资产,并铸造相应数量的金库份额给 receiver 地址。mint(uint shares, address receiver) 与它类似,只不过是以将铸造的金库份额作为参数。
  3. 提款逻辑:让用户销毁金库份额,并提取金库中相应数量的基础资产。相关函数为 withdraw()redeem(),前者以取出基础资产数量为参数,后者以销毁的金库份额为参数。
  4. 会计和限额逻辑:ERC4626 标准中其他的函数是为了统计金库中的资产,存款/提款限额,和存款/提款的基础资产和金库份额数量。

IERC4626 接口合约

IERC4626 接口合约共包含 2 个事件:

  • Deposit 事件: 存款时触发。
  • Withdraw 事件: 取款时触发。

IERC4626 接口合约还包含 16 个函数,根据功能分为 4 大类:元数据,存款/提款逻辑,会计逻辑,和存款/提款限额逻辑。

  • 元数据

    • asset(): 返回金库的基础资产代币地址,用于存款,取款。
  • 存款/提款逻辑

    • deposit(): 存款函数,用户向金库存入 assets 单位的基础资产,然后合约铸造 shares 单位的金库额度给 receiver 地址。会释放 Deposit 事件。
    • mint(): 铸造函数(也是存款函数),用户存入 assets 单位的基础资产,然后合约给 receiver 地址铸造相应数量的金库额度。会释放 Deposit 事件。
    • withdraw(): 提款函数,owner 地址销毁 share 单位的金库额度,然后合约将相应数量的基础资产发送给 receiver 地址。
    • redeem(): 赎回函数(也是提款函数),owner 地址销毁 shares 数量的金库额度,然后合约将相应单位的基础资产发给 receiver 地址
  • 会计逻辑

    • totalAssets(): 返回金库中管理的基础资产代币总额。
    • convertToShares(): 返回利用一定数额基础资产可以换取的金库额度。
    • convertToAssets(): 返回利用一定数额金库额度可以换取的基础资产。
    • previewDeposit(): 用于用户在当前链上环境模拟存款一定数额的基础资产能够获得的金库额度。
    • previewMint(): 用于用户在当前链上环境模拟铸造一定数额的金库额度需要存款的基础资产数量。
    • previewWithdraw(): 用于用户在当前链上环境模拟提款一定数额的基础资产需要赎回的金库份额。
    • previewRedeem(): 用于链上和链下用户在当前链上环境模拟销毁一定数额的金库额度能够赎回的基础资产数量。
  • 存款/提款限额逻辑

    • maxDeposit(): 返回某个用户地址单次存款可存的最大基础资产数额。
    • maxMint(): 返回某个用户地址单次铸造可以铸造的最大金库额度。
    • maxWithdraw(): 返回某个用户地址单次取款可以提取的最大基础资产额度。
    • maxRedeem(): 返回某个用户地址单次赎回可以销毁的最大金库额度。
// SPDX-License-Identifier: MIT
// Author: 0xAA from WTF Academy

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";

/**
 * @dev ERC4626 "代币化金库标准"的接口合约
 * https://eips.ethereum.org/EIPS/eip-4626[ERC-4626].
 */
interface IERC4626 is IERC20, IERC20Metadata {
    /*//
                                 事件
    //*/
    // 存款时触发
    event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);

    // 取款时触发
    event Withdraw(
        address indexed sender,
        address indexed receiver,
        address indexed owner,
        uint256 assets,
        uint256 shares
    );

    /*//
                            元数据
    //*/
    /**
     * @dev 返回金库的基础资产代币地址 (用于存款,取款)
     * - 必须是 ERC20 代币合约地址.
     * - 不能revert
     */
    function asset() external view returns (address assetTokenAddress);

    /*//
                        存款/提款逻辑
    //*/
    /**
     * @dev 存款函数: 用户向金库存入 assets 单位的基础资产,然后合约铸造 shares 单位的金库额度给 receiver 地址
     *
     * - 必须释放 Deposit 事件.
     * - 如果资产不能存入,必须revert,比如存款数额大大于上限等。
     */
    function deposit(uint256 assets, address receiver) external returns (uint256 shares);

    /**
     * @dev 铸造函数: 用户需要存入 assets 单位的基础资产,然后合约给 receiver 地址铸造 share 数量的金库额度
     * - 必须释放 Deposit 事件.
     * - 如果全部金库额度不能铸造,必须revert,比如铸造数额大大于上限等。
     */
    function mint(uint256 shares, address receiver) external returns (uint256 assets);

    /**
     * @dev 提款函数: owner 地址销毁 share 单位的金库额度,然后合约将 assets 单位的基础资产发送给 receiver 地址
     * - 释放 Withdraw 事件
     * - 如果全部基础资产不能提取,将revert
     */
    function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);

    /**
     * @dev 赎回函数: owner 地址销毁 shares 数量的金库额度,然后合约将 assets 单位的基础资产发给 receiver 地址
     * - 释放 Withdraw 事件
     * - 如果金库额度不能全部销毁,则revert
     */
    function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);

    /*//
                            会计逻辑
    //*/

    /**
     * @dev 返回金库中管理的基础资产代币总额
     * - 要包含利息
     * - 要包含费用
     * - 不能revert
     */
    function totalAssets() external view returns (uint256 totalManagedAssets);

    /**
     * @dev 返回利用一定数额基础资产可以换取的金库额度
     * - 不要包含费用
     * - 不包含滑点
     * - 不能revert
     */
    function convertToShares(uint256 assets) external view returns (uint256 shares);

    /**
     * @dev 返回利用一定数额金库额度可以换取的基础资产
     * - 不要包含费用
     * - 不包含滑点
     * - 不能revert
     */
    function convertToAssets(uint256 shares) external view returns (uint256 assets);

    /**
     * @dev 用于链上和链下用户在当前链上环境模拟存款一定数额的基础资产能够获得的金库额度
     * - 返回值要接近且不大于在同一交易进行存款得到的金库额度
     * - 不要考虑 maxDeposit 等限制,假设用户的存款交易会成功
     * - 要考虑费用
     * - 不能revert
     * NOTE: 可以利用 convertToAssets 和 previewDeposit 返回值的差值来计算滑点
     */
    function previewDeposit(uint256 assets) external view returns (uint256 shares);

    /**
     * @dev 用于链上和链下用户在当前链上环境模拟铸造 shares 数额的金库额度需要存款的基础资产数量
     * - 返回值要接近且不小于在同一交易进行铸造一定数额金库额度所需的存款数量
     * - 不要考虑 maxMint 等限制,假设用户的存款交易会成功
     * - 要考虑费用
     * - 不能revert
     */
    function previewMint(uint256 shares) external view returns (uint256 assets);

    /**
     * @dev 用于链上和链下用户在当前链上环境模拟提款 assets 数额的基础资产需要赎回的金库份额
     * - 返回值要接近且不大于在同一交易进行提款一定数额基础资产所需赎回的金库份额
     * - 不要考虑 maxWithdraw 等限制,假设用户的提款交易会成功
     * - 要考虑费用
     * - 不能revert
     */
    function previewWithdraw(uint256 assets) external view returns (uint256 shares);

    /**
     * @dev 用于链上和链下用户在当前链上环境模拟销毁 shares 数额的金库额度能够赎回的基础资产数量
     * - 返回值要接近且不小于在同一交易进行销毁一定数额的金库额度所能赎回的基础资产数量
     * - 不要考虑 maxRedeem 等限制,假设用户的赎回交易会成功
     * - 要考虑费用
     * - 不能revert.
     */
    function previewRedeem(uint256 shares) external view returns (uint256 assets);

    /*//
                     存款/提款限额逻辑
    //*/
    /**
     * @dev 返回某个用户地址单次存款可存的最大基础资产数额。
     * - 如果有存款上限,那么返回值应该是个有限值
     * - 返回值不能超过 2 ** 256 - 1 
     * - 不能revert
     */
    function maxDeposit(address receiver) external view returns (uint256 maxAssets);

    /**
     * @dev 返回某个用户地址单次铸造可以铸造的最大金库额度
     * - 如果有铸造上限,那么返回值应该是个有限值
     * - 返回值不能超过 2 ** 256 - 1 
     * - 不能revert
     */
    function maxMint(address receiver) external view returns (uint256 maxShares);

    /**
     * @dev 返回某个用户地址单次取款可以提取的最大基础资产额度
     * - 返回值应该是个有限值
     * - 不能revert
     */
    function maxWithdraw(address owner) external view returns (uint256 maxAssets);

    /**
     * @dev 返回某个用户地址单次赎回可以销毁的最大金库额度
     * - 返回值应该是个有限值
     * - 如果没有其他限制,返回值应该是 balanceOf(owner)
     * - 不能revert
     */
    function maxRedeem(address owner) external view returns (uint256 maxShares);
}

ERC4626 合约

下面,我们实现一个极简版的代币化金库合约:

  • 构造函数初始化基础资产的合约地址,金库份额的代币名称和符号。注意,金库份额的代币名称和符号要和基础资产有关联,比如基础资产叫 WTF,金库份额最好叫 vWTF
  • 存款时,当用户向金库存 x 单位的基础资产,会铸造 x 单位(等量)的金库份额。
  • 取款时,当用户销毁 x 单位的金库份额,会提取 x 单位(等量)的基础资产。

注意: 在实际使用时,要特别小心和会计逻辑相关函数的计算是向上取整还是向下取整,可以参考 openzeppelin 和 solmate 的实现。本节的教学例子中不考虑它。

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import {IERC4626} from "./IERC4626.sol";
import {ERC20, IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

/**
 * @dev ERC4626 "代币化金库标准"合约,仅供教学使用,不要用于生产
 */
contract ERC4626 is ERC20, IERC4626 {
    /*//
                    状态变量
    //*/
    ERC20 private immutable _asset; // 
    uint8 private immutable _decimals;

    constructor(
        ERC20 asset_,
        string memory name_,
        string memory symbol_
    ) ERC20(name_, symbol_) {
        _asset = asset_;
        _decimals = asset_.decimals();

    }

    /** @dev See {IERC4626-asset}. */
    function asset() public view virtual override returns (address) {
        return address(_asset);
    }

    /**
     * See {IERC20Metadata-decimals}.
     */
    function decimals() public view virtual override(IERC20Metadata, ERC20) returns (uint8) {
        return _decimals;
    }

    /*//
                        存款/提款逻辑
    //*/
    /** @dev See {IERC4626-deposit}. */
    function deposit(uint256 assets, address receiver) public virtual returns (uint256 shares) {
        // 利用 previewDeposit() 计算将获得的金库份额
        shares = previewDeposit(assets);

        // 先 transfer 后 mint,防止重入
        _asset.transferFrom(msg.sender, address(this), assets);
        _mint(receiver, shares);

        // 释放 Deposit 事件
        emit Deposit(msg.sender, receiver, assets, shares);
    }

    /** @dev See {IERC4626-mint}. */
    function mint(uint256 shares, address receiver) public virtual returns (uint256 assets) {
        // 利用 previewMint() 计算需要存款的基础资产数额
        assets = previewMint(shares);

        // 先 transfer 后 mint,防止重入
        _asset.transferFrom(msg.sender, address(this), assets);
        _mint(receiver, shares);

        // 释放 Deposit 事件
        emit Deposit(msg.sender, receiver, assets, shares);

    }

    /** @dev See {IERC4626-withdraw}. */
    function withdraw(
        uint256 assets,
        address receiver,
        address owner
    ) public virtual returns (uint256 shares) {
        // 利用 previewWithdraw() 计算将销毁的金库份额
        shares = previewWithdraw(assets);

        // 如果调用者不是 owner,则检查并更新授权
        if (msg.sender != owner) {
            _spendAllowance(owner, msg.sender, shares);
        }

        // 先销毁后 transfer,防止重入
        _burn(owner, shares);
        _asset.transfer(receiver, assets);

        // 释放 Withdraw 事件
        emit Withdraw(msg.sender, receiver, owner, assets, shares);
    }

    /** @dev See {IERC4626-redeem}. */
    function redeem(
        uint256 shares,
        address receiver,
        address owner
    ) public virtual returns (uint256 assets) {
        // 利用 previewRedeem() 计算能赎回的基础资产数额
        assets = previewRedeem(shares);

        // 如果调用者不是 owner,则检查并更新授权
        if (msg.sender != owner) {
            _spendAllowance(owner, msg.sender, shares);
        }

        // 先销毁后 transfer,防止重入
        _burn(owner, shares);
        _asset.transfer(receiver, assets);

        // 释放 Withdraw 事件       
        emit Withdraw(msg.sender, receiver, owner, assets, shares);
    }

    /*//
                            会计逻辑
    //*/
    /** @dev See {IERC4626-totalAssets}. */
    function totalAssets() public view virtual returns (uint256){
        // 返回合约中基础资产持仓
        return _asset.balanceOf(address(this));
    }

    /** @dev See {IERC4626-convertToShares}. */
    function convertToShares(uint256 assets) public view virtual returns (uint256) {
        uint256 supply = totalSupply();
        // 如果 supply 为 0,那么 1:1 铸造金库份额
        // 如果 supply 不为0,那么按比例铸造
        return supply == 0 ? assets : assets * supply / totalAssets();
    }

    /** @dev See {IERC4626-convertToAssets}. */
    function convertToAssets(uint256 shares) public view virtual returns (uint256) {
        uint256 supply = totalSupply();
        // 如果 supply 为 0,那么 1:1 赎回基础资产
        // 如果 supply 不为0,那么按比例赎回
        return supply == 0 ? shares : shares * totalAssets() / supply;
    }

    /** @dev See {IERC4626-previewDeposit}. */
    function previewDeposit(uint256 assets) public view virtual returns (uint256) {
        return convertToShares(assets);
    }

    /** @dev See {IERC4626-previewMint}. */
    function previewMint(uint256 shares) public view virtual returns (uint256) {
        return convertToAssets(shares);
    }

    /** @dev See {IERC4626-previewWithdraw}. */
    function previewWithdraw(uint256 assets) public view virtual returns (uint256) {
        return convertToShares(assets);
    }

    /** @dev See {IERC4626-previewRedeem}. */
    function previewRedeem(uint256 shares) public view virtual returns (uint256) {
        return convertToAssets(shares);
    }

    /*//
                     存款/提款限额逻辑
    //*/
    /** @dev See {IERC4626-maxDeposit}. */
    function maxDeposit(address) public view virtual returns (uint256) {
        return type(uint256).max;
    }

    /** @dev See {IERC4626-maxMint}. */
    function maxMint(address) public view virtual returns (uint256) {
        return type(uint256).max;
    }
    
    /** @dev See {IERC4626-maxWithdraw}. */
    function maxWithdraw(address owner) public view virtual returns (uint256) {
        return convertToAssets(balanceOf(owner));
    }
    
    /** @dev See {IERC4626-maxRedeem}. */
    function maxRedeem(address owner) public view virtual returns (uint256) {
        return balanceOf(owner);
    }
}

Remix演示

注意: 以下运行示例使用了remix中第二个账户,即0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2, 来部署合约, 调用合约方法.

  1. 部署 ERC20 代币合约,将代币名称和符号均设为 WTF,并给自己铸造 10000 代币。
    51-2-1.png
    51-2-2.png

  2. 部署 ERC4626 代币合约,将基础资产的合约地址设为 WTF 的地址,名称和符号均设为 vWTF
    51-3.png

  3. 调用 ERC20 合约的 approve() 函数,将代币授权给 ERC4626 合约。
    51-4.png

  4. 调用 ERC4626 合约的 deposit() 函数,存款 1000 枚代币。然后调用 balanceOf() 函数,查看自己的金库份额变为 1000
    51-5.png

  5. 调用 ERC4626 合约的 mint() 函数,存款 1000 枚代币。然后调用 balanceOf() 函数查看自己的金库份额变为 2000
    51-6.png

  6. 调用 ERC4626 合约的 withdraw() 函数,取款 1000 枚代币。然后调用 balanceOf() 函数查看自己的金库份额变为 1000
    51-7.png

  7. 调用 ERC4626 合约的 redeem() 函数,取款 1000 枚代币。然后调用 balanceOf() 函数查看自己的金库份额变为 0
    51-8.png

总结

这一讲,我们介绍了代币化金库标准 ERC4626,并写了一个简单的金库合约,可以将基础资产 1:1 的转换为金库份额代币。ERC4626 为 DeFi 提升流动性和可组合性,未来将逐渐普及。你会用 ERC4626 做什么应用呢?

在这里插入图片描述

如果这份博客对大家有帮助,希望各位给作者一个免费的点赞👍作为鼓励,并评论收藏一下⭐,谢谢大家!!!
制作不易,如果大家有什么疑问或给作者的意见,欢迎评论区留言。

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

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

相关文章

https跳过SSL认证时是不是就是不加密的,相当于http?

https跳过SSL认证时是不是就是不加密的,相当于http?,其实不是,HTTPS跳过SSL认证并不相当于HTTP,也不意味着没有加密。请注意以下几点: HTTPS(Hypertext Transfer Protocol Secure)本质上是在HTTP的基础上…

在docker中删除none镜像

在构建过Docker镜像的电脑上查看本地镜像列表,有可能看到下图红框中的镜像,在列表中展示为:: 这种镜像在Docker官方文档中被称作dangling images,指的是没有标签并且没有被容器使用的镜像。 官方解释 来自官方的解释如下图红框所…

【多线程初阶】多线程案例之阻塞式队列

文章目录 前言1. 什么是阻塞队列2. 生产者消费者模型2.1 生产者消费者模型的优势2.1.1 解耦合2.1.2 削峰填谷 3. Java 标准库中的阻塞队列3.1 生产者消费者模型 4. 自己实现一个阻塞队列总结 前言 本文主要给大家讲解多线程的一个重要案例 — 阻塞式队列. 关注收藏, 开始学习…

如何在Windows11上使用macOS Sonoma全新的慢镜屏幕保护程序

前言 macOS Sonoma是Apple macOS一个大版本的描述,以任何方式使用macOS Sonoma都应确保符合Apple的规定 本文假定你在搭载Intel处理器的Apple产品上通过bootcamp安装了Windows11,且想要让Windows11产生类似于macOS Sonoma全新的慢镜屏幕保护程序的相关…

java面试题-jvm基础知识

1 JVM组成 1.1 JVM由那些部分组成,运行流程是什么? 难易程度:☆☆☆ 出现频率:☆☆☆☆ JVM是什么 Java Virtual Machine Java程序的运行环境(java二进制字节码的运行环境) 好处: 一次编写&a…

【记录文】Android自定义Dialog实现圆角对话框

圆角的dialog还是蛮常用的,demo中正好用上了 自定义Dialog,代码中可以设置指定大小与位置 /*** author : jiangxue* date : 2023/9/25 13:21* description :圆角的矩形*/internal class RoundCornerView(context: Context,view: Int, StyleRes theme…

智能网联驾驶测试与评价工业和信息化部重点实验室“车载智能计算基础平台参考架构2.0专家研讨会”圆满结束

近日,智能网联驾驶测试与评价工业和信息化部重点实验室在北京市召开“车载智能计算基础平台参考架构2.0专家研讨会”,本次会议由智能网联驾驶测试与评价工业和信息化部重点实验室、中国软件评测中心(工业和信息化部软件与集成电路促进中心&am…

美颜SDK哪家好?2023美颜SDK有哪些新玩法?

在当今的数字世界中,美颜SDK正成为一种强大的工具,可以帮助我们在视频直播和短视频中展现出最美的自己。美摄科技作为一家专注于美颜SDK技术的公司,提供了多种领先的美颜SDK相关产品,以满足不同用户的需求。 美摄科技的美颜SDK是一…

第一章 计算机网络与协议

文章目录 一、计算机网络的基础概念二、计算机网络分类2.1 通信子网/资源子网/网络协议2.2 网络拓补图分类2.3 按照覆盖范围分类2.4 按照交换技术分类2.5 其他分类 三、OSI参考模型3.1 应用层3.2 表示层3.3 会话层3.4 传输层3.5 网络层3.6 数据链路层3.7 物理层3.8 借助OSI模型…

众佰诚:现在开一家抖音小店还来得及吗

随着互联网的迅猛发展,电商行业也进入了一个全新的时代,其中抖音小店作为新兴的销售平台备受瞩目。然而,对于那些考虑开设抖音小店的人来说,一个重要的问题是:现在开一家抖音小店还来得及吗? 答案是肯定的&#xff0c…

Vulnhub-DC-8 靶机复现完整过程

Vulnhub-DC-8 靶机复现完整过程 一、环境搭建 kali的IP地址:192.168.200.14 DC-8的IP地址:192.168.200.13(一个flag) 靶机和攻击机处于同一个网络方式:nat或桥接 若出现开机错误,适当将dc的兼容版本改低…

纷享销客荣获“最佳用户支持与服务奖”

近日,e签宝第二届用户生态峰会在上海如约而至。纷享销客作为e签宝生态合作伙伴,荣获“最佳用户支持与服务奖”。 数字化时代,孤岛式、断裂式、封闭式的数字化,不仅无法为客户带来价值,对提供数字化服务的双方而言&…

latex subsection 第一段 首行取消缩进

需求:在\subsection 标题下的第一段,取消首行缩进。 (此时直接使用 \noindent 命令,失效) 环境:IEEE 模板 解决方案: 增加一个空行,使有效内容行变为第二行,然后对其…

SQLAlchemy常用数据类型

目录 SQLAlchemy常用数据类型 代码演示 代码分析 SQLAlchemy常用数据类型 SQLAlchemy 是一个Python的SQL工具库和对象关系映射(ORM)工具,它提供了一种在Python中操作数据库的高效方式。下面是SQLAlchemy中常用的一些数据类型: Integer:整形&…

体验亚马逊的 CodeWhisperer 感觉

CodeWhisperer 是亚马逊推出的辅助编程工具,在程序员写代码时,它能根据其内容生成多种代码建议。 CodeWhisperer 目前已支持近10几种语言,我是用 java 语言,用的开发工具是 idea,说一下我用的情况。 亚马逊云科技开发…

四川玖璨电子商务有限公司正规吗?

​近年来,随着社交平台的兴起,带货已经成为了一种新型商业模式。抖音作为中国最大的短视频平台之一,也成为了众多商家进行带货销售的重要渠道。而在这个过程中,公司如四川玖璨电子商务有限公司也逐渐涌现出来,为广大用…

什么是 Redis?

Redis 是一种基于内存的数据库,对数据的读写操作都是在内存中完成的,因此读写速度非常快,常用于缓存,消息队列,分布式锁等场景。 Redis 提供了多种数据类型来支持不同的业务场景,比如 String(字符串)、Has…

Spring源码解析—— IOC默认标签解析(下)

正文 在上一篇我们已经完成了从xml配置文件到BeanDefinition的转换,转换后的实例是GenericBeanDefinition的实例。本文主要来看看标签解析剩余部分及BeanDefinition的注册。 默认标签中的自定义标签解析 在上篇博文中我们已经分析了对于默认标签的解析&#xff0…

vue 组件通信

vue 组件通信 mytitile 是变量 title 是形参 回调函数

RocketMQ 线上问题处理

一、发送超时后,重试发送 1.1、问题 生产者发送超时,进行重试发送。 1.2、解决 消费端做幂等处理。