智能合约中approve函数详解

news2024/9/9 7:27:32

场景

这段时间很多小伙伴加我、都咨询到了一个类似的业务场景、
如下:

  • 1、第一步业务里面调用授权函数approve 、给指定address
  • 2、第二步是由授权的address调用transferFrom转移给指定的接受地址。
    案例DEMO如下(这里test2肯定是会执行失败的)
    在这里插入图片描述

我问他为什么不只用Token的标准approve 函数外部提前调用一次、然后直接执行第二步就行了、他们都有各自的理由、咱也不好细问。

先解释标准合约中approve 的实现原理,咱们就知道为什么test2会调用失败;

以及要完成这个业务需求该如何改造。

一、标准Toekn

一般标准toen都会实现IERC20协议

1.1 IERC20协议

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the value of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 value) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 value) external returns (bool);


    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}

1.2 approve 授权函数

在这里插入图片描述

解释:external

这里的接口的函数都有一个特点都是由 external 修饰的。
一般在智能合约中,external 修饰符用于限定方法的可见性和调用方式。当一个方法被标记为 external 时,它表示该方法只能从外部账户(即非智能合约账户)调用。这意味着该方法不能被同一个智能合约内部的其他方法直接调用,也不能被其他智能合约调用。

1.3 标准的Token合约实现IERC20的下这样的、

如下

contract BEP20Token is Context, IERC20, Ownable {
  using SafeMath for uint256;

  mapping (address => uint256) private _balances;

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

  uint256 private _totalSupply;
  uint8 private _decimals;
  string private _symbol;
  string private _name;

  constructor() public {
    _name = " ";
    _symbol =" "  ;
    _decimals = 18;
    _totalSupply = 10000000 *10**18;
    _balances[msg.sender] = _totalSupply;

    emit Transfer(address(0), msg.sender, _totalSupply);
  }

  /**
   * @dev Returns the bep token owner.
   */
  function getOwner() external view returns (address) {
    return owner();
  }

  /**
   * @dev Returns the token decimals.
   */
  function decimals() external view returns (uint8) {
    return _decimals;
  }

  /**
   * @dev Returns the token symbol.
   */
  function symbol() external view returns (string memory) {
    return _symbol;
  }

  /**
  * @dev Returns the token name.
  */
  function name() external view returns (string memory) {
    return _name;
  }

  /**
   * @dev See {BEP20-totalSupply}.
   */
  function totalSupply() external view returns (uint256) {
    return _totalSupply;
  }

  /**
   * @dev See {BEP20-balanceOf}.
   */
  function balanceOf(address account) external view returns (uint256) {
    return _balances[account];
  }

  /**
   * @dev See {BEP20-transfer}.
   *
   * Requirements:
   *
   * - `recipient` cannot be the zero address.
   * - the caller must have a balance of at least `amount`.
   */
  function transfer(address recipient, uint256 amount) external returns (bool) {
    _transfer(_msgSender(), recipient, amount);
    return true;
  }

  /**
   * @dev See {BEP20-allowance}.
   */
  function allowance(address owner, address spender) external view returns (uint256) {
    return _allowances[owner][spender];
  }

  /**
   * @dev See {BEP20-approve}.
   *
   * Requirements:
   *
   * - `spender` cannot be the zero address.
   */
  function approve(address spender, uint256 amount) external returns (bool) {
    _approve(_msgSender(), spender, amount);
    return true;
  }

  
 
  /**
   * @dev See {BEP20-transferFrom}.
   *
   * Emits an {Approval} event indicating the updated allowance. This is not
   * required by the EIP. See the note at the beginning of {BEP20};
   *
   * Requirements:
   * - `sender` and `recipient` cannot be the zero address.
   * - `sender` must have a balance of at least `amount`.
   * - the caller must have allowance for `sender`'s tokens of at least
   * `amount`.
   */
  function transferFrom(address sender, address recipient, uint256 amount) external returns (bool) {
    _transfer(sender, recipient, amount);
    _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "BEP20: transfer amount exceeds allowance"));
    return true;
  }

  /**
   * @dev Atomically increases the allowance granted to `spender` by the caller.
   *
   * This is an alternative to {approve} that can be used as a mitigation for
   * problems described in {BEP20-approve}.
   *
   * Emits an {Approval} event indicating the updated allowance.
   *
   * Requirements:
   *
   * - `spender` cannot be the zero address.
   */
  function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
    _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
    return true;
  }

  /**
   * @dev Atomically decreases the allowance granted to `spender` by the caller.
   *
   * This is an alternative to {approve} that can be used as a mitigation for
   * problems described in {BEP20-approve}.
   *
   * Emits an {Approval} event indicating the updated allowance.
   *
   * Requirements:
   *
   * - `spender` cannot be the zero address.
   * - `spender` must have allowance for the caller of at least
   * `subtractedValue`.
   */
  function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
    _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "BEP20: decreased allowance below zero"));
    return true;
  }
 

  /**
   * @dev Moves tokens `amount` from `sender` to `recipient`.
   *
   * This is internal function is equivalent to {transfer}, and can be used to
   * e.g. implement automatic token fees, slashing mechanisms, etc.
   *
   * Emits a {Transfer} event.
   *
   * Requirements:
   *
   * - `sender` cannot be the zero address.
   * - `recipient` cannot be the zero address.
   * - `sender` must have a balance of at least `amount`.
   */
  function _transfer(address sender, address recipient, uint256 amount) internal {
    require(sender != address(0), "BEP20: transfer from the zero address");
    require(recipient != address(0), "BEP20: transfer to the zero address");

    _balances[sender] = _balances[sender].sub(amount, "BEP20: transfer amount exceeds balance");
    _balances[recipient] = _balances[recipient].add(amount);
    emit Transfer(sender, recipient, amount);
  }

 
  /**
   * @dev Destroys `amount` tokens from `account`, reducing the
   * total supply.
   *
   * Emits a {Transfer} event with `to` set to the zero address.
   *
   * Requirements
   *
   * - `account` cannot be the zero address.
   * - `account` must have at least `amount` tokens.
   */
  function _burn(address account, uint256 amount) internal {
    require(account != address(0), "BEP20: burn from the zero address");

    _balances[account] = _balances[account].sub(amount, "BEP20: burn amount exceeds balance");
    _totalSupply = _totalSupply.sub(amount);
    emit Transfer(account, address(0), amount);
  }

  /**
   * @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens.
   *
   * This is internal function is equivalent to `approve`, and can be used to
   * e.g. set automatic allowances for certain subsystems, etc.
   *
   * Emits an {Approval} event.
   *
   * Requirements:
   *
   * - `owner` cannot be the zero address.
   * - `spender` cannot be the zero address.
   */
  function _approve(address owner, address spender, uint256 amount) internal {
    require(owner != address(0), "BEP20: approve from the zero address");
    require(spender != address(0), "BEP20: approve to the zero address");

    _allowances[owner][spender] = amount;
    emit Approval(owner, spender, amount);
  }

  /**
   * @dev Destroys `amount` tokens from `account`.`amount` is then deducted
   * from the caller's allowance.
   *
   * See {_burn} and {_approve}.
   */
  function _burnFrom(address account, uint256 amount) internal {
    _burn(account, amount);
    _approve(account, _msgSender(), _allowances[account][_msgSender()].sub(amount, "BEP20: burn amount exceeds allowance"));
  }
}

咱们看一下Token里面标准的函数
在这里插入图片描述

1.4 案例出现的问题分析

    function test() public    {
          toekn.approve(  address(this), 10000000000000000000);
        
    }

执行步骤

1、外部调用test函数时
2、函数再调用 toekn.approve
3、此时toekn的approve函数再调用 _approve(_msgSender(), spender, amount);

执行步骤分析

这里有一个 _msgSender() 函数、关键就是这里了。当外部合约调用的Token合约的之时、 _msgSender() 函数本身调用结果就是 外部合约本身地址!!!
很多同学以为的触发外部函数的 地址、故此from地址把token授权给了合约地址。殊不知是 外部合约 授权给了授权地址。

到此大部分同学已经明白了原理了、但…很快又有同学问、既然如此、我们何不直接调用
_approve 函数,直接传入 咱们的 参数不就好了么…
咱们再来看看 _approve 函数
在这里插入图片描述

internal 修饰符的作用
  1. 访问控制:
    • internal 方法只能在同一个智能合约内部被其他方法调用。
    • 这有助于保护智能合约内部的逻辑不受外部调用的影响,从而增强安全性。

在智能合约中,internal 修饰符用于限定方法的可见性和调用方式。当一个方法被标记为 internal 时,它表示该方法只能在同一个智能合约内部被其他方法调用。这意味着该方法不能被外部账户或来自其他智能合约的调用直接访问。

由此可知 _approve 函数是不支持外部调用的,故此是行不通。

二、标准Toekn改造

其实经过上面的原理解释、改造的核心就是对外提供的approve函数。

  function approve(address sendr,address spender, uint256 amount) external returns (bool) {
    _approve(sendr, spender, amount);
    return true;
  }

把sendr函数暴露出来,给外部调用

contract ArrayDeletionByMovingElements {
    IERC20 public toekn;

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

    constructor(IERC20 _token) {
        toekn = _token;
    }

    function test() public    {
          toekn.approve( msg.sender,address(this), 10000000000000000000);
        
    }

    function test2() public {
         toekn.transferFrom(msg.sender,  address(this), 1000);
         emit Transfer(msg.sender, address(this), 1000);
    }
}

不过还是建议走外部函数授权的形式,改造标准Token不可取…、参考市场主流的操作流程较好。

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

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

相关文章

C语言典型例题19

《C程序设计教程(第四版)——谭浩强》 例题2.7 检查浮点型数据的舍去误差 C语言知识: 浮点数在C语言用有两个类型,有float和double类型,其中double类型的数据精度更高 解题思路: 可以将一个double类型的…

城市运管服平台:智慧城市的神经中枢

“城市运行管理服务平台”(简称“城市运管服平台”)作为现代城市治理的智慧核心,正以前所未有的方式重塑着城市管理与服务的面貌。本文旨在深度解析城市运管服平台的内涵、运作机制及其对推动智慧城市发展的重要意义。 城市运管服平台&#…

视频超压缩保持质量 ffmpeg

参考: https://x.com/mortenjust/status/1817991110544744764 基于 FFMpeg 的 H264 压缩标准,实现压缩 90% 的视频大小 在线体验地址: https://tools.rotato.app/compress ffmpeg命令执行 ffmpeg -i "C:\Users\loong\Downloads\屏幕录…

Vidu视频生成工具全球上线,注册即刻体验

生数科技,这家成立于2023年3月的年轻企业,其核心团队成员来自清华大学人工智能研究院。首席科学家朱军教授,曾在贝叶斯机器学习和多模态大模型领域取得多项原创性成果。Vidu的核心技术U-ViT架构,是全球首个将Diffusion与Transform…

# mongodb_基础到进阶 -- MongoDB 快速上手(四)

mongodb_基础到进阶 – MongoDB 快速上手(四) 一、MongoDB :文章评论 需求&表结构&技术选型 1、文章评论:需求分析 1)参考某头条的文章评论业务,文章示例参考:早晨空腹喝水&#xff…

功能强大的图片/pdf识别成文字的工具Umi-OCR

一、工具概述 操作系统:支持windows、Linux使用方法:图形化、命令行、http接口 二、功能特性 三、部署方式 1、windows 下载地址:Umi-OCR_文字识别工具 2、linux 推荐使用docker部署 》下载 Dockerfile mkdir Umi-OCR-Docker cd Umi-OC…

Overlay网络

Overlay 介绍 Overlay网络是将已有的物理网络(Underlay网络)作为基础,在其上建立叠加的逻辑网络,实现网络资源的虚拟化。 传统网络带来了以下一些问题: ● 虚拟机规模受 网络规格限制在传统二层网络环境下&#xff0…

某“新一线城市“前端面试,纯纯被恶心到了

一.起因 在boss上刷到的一个小小厂,收大四全年实习生(web前端),因为离家乡比较近,而且双非一本的学历以及入坑的时间没有太长(前端部分不到一年),想去那里看看形势,要求是笔试面试,当场给是否录用结果&…

WPS@文字文档插入的表格,对单元格填充底纹颜色时,有字的地方就填充不上 ,空白。

背景 在创建的WPS文字文档中,插入了一个表格,对表格的某些单元格需要填充底纹颜色 问题 问题如标题,“文字文档插入的表格,对单元格填充底纹颜色时,有字的地方就填充不上 ,空白”,直观如下图 解…

【Linux】-----进度条小程序

目录 前言 基本知识 Ⅰ、回车和换行 Ⅱ、缓冲区 两个有意思的现象 简单定义 刷新缓冲区 简易倒计时程序 进度条代码 多文件下makefile写法 一代(无任何场景) procs1.h代码 procs1.c代码 主函数main1.c 一代运行结果: 二代 (搭配下载场景) procs2.c代…

Qt Creator使用git管理代码

1.在GitHub中新建仓库,设置好仓库名后,其它的设置默认即可。 2.打开git bash,输入以下命令: git config --global user.name "xxxxx" #设置你的GitHub用户名 git config --global user.email "xxxxxxxxx.…

大厂linux面试题攻略四之Linux网络服务(二)

五、Linux网络服务-Apache优化 1.请写出工作中常见的Apache优化策略 Apache服务器优化是提升网站响应速度和稳定性的重要手段。在工作中,常见的Apache优化策略包括以下几个方面: 1. 启用压缩技术 Gzip压缩:使用Gzip压缩技术可以减少服务器…

免费【2024】springboot 宠物医院微信小程序的设计与实现

博主介绍:✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌ 技术范围:SpringBoot、Vue、SSM、HTML、Jsp、PHP、Nodejs、Python、爬虫、数据可视化…

Python(关于属性和类详细代码解释)

动态绑定属性 class Student:# 类属性:定义在类中,方法外的变量school 北京xxx教育print(type(school)) # 定义在类里面所以属性不变# 类属性初始化方法def __init__(self, xm, age): # xm,age是方法的参数,是局部变量,xm,age…

解决Centos不支持docker命令行tab提示问题!!!

一、CentOS不支持Docker Tab提示 在使用CentOS操作系统时,有些用户可能会遇到不能自动补全Docker命令的问题。这是因为CentOS默认不支持Docker Tab提示功能,需要手动配置才能实现。在这篇科普文章中,我们将介绍如何解决这个问题,…

Springer旗下中科院2区TOP,国人优势大!

关注GZH【欧亚科睿学术】,第一时间了解期刊最新动态! 1 通信网络类 【期刊简介】IF:4.0-5.0,JCR1区,中科院3区 【出版社】ELSEVIER出版社 【检索情况】SCIE&EI双检,CCF-C类 【征稿领域】通信网络的…

这本vue3编译原理开源电子书,初中级前端竟然都能看懂

前言 众所周知vue提供了很多黑魔法,比如单文件组件(SFC)、指令、宏函数、css scoped等。这些都是vue提供的开箱即用的功能,大家平时用这些黑魔法的时候有没有疑惑过一些疑问呢。 我们每天写的vue代码一般都是写在*.vue文件中,但是浏览器却只…

大模型面试之LoRA

LoRA的解释:一种高效微调预训练神经网络的方法 LoRA 解决的问题: 🔸 2021年初,微软与OpenAI合作探索GPT-3的商业可行性。 🔸 发现仅仅通过提示(prompting)不足以完成生产任务,例如…

C++:标准模板库(STL)介绍

1.STL基本概念 从C到C,C语言的核心优势之一就是便于软件的重用。前面我们提到过C程序的面向对象思想,即继承和多态、标准类库等可以实现重用。除此之外,通过泛型程序设计(generic programming)的思想,即模板机制以及标准模板库ST…

微软:警惕利用VMware ESXi进行身份验证绕过攻击

微软于7月29日发布警告,称勒索软件团伙正在积极利用 VMware ESXi 身份验证绕过漏洞进行攻击。 该漏洞被追踪为 CVE-2024-37085,由微软安全研究人员 Edan Zwick、Danielle Kuznets Nohi 和 Meitar Pinto 发现,并在 6 月 25 日发布的 ESXi 8.0 …