发行ERC20代币后后悔了怎么办?详解将所有ERC20代币迁移到新合约的方法

栏目: IT技术 · 发布时间: 4年前

内容简介:当我们发行了ERC20代币之后,因为某些特殊原因不得不放弃掉ERC20的智能合约,而改用新合约的时候,可以通过布署一个迁移合约的方法实现将旧合约的所有代币迁移到新合约的需求.本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

当我们发行了ERC20代币之后,因为某些特殊原因不得不放弃掉ERC20的智能合约,而改用新合约的时候,可以通过布署一个迁移合约的方法实现将旧合约的所有代币迁移到新合约的需求.

创建运行环境

1. 首先要初始化环境

$ npm init -y	//初始化npm环境
$ npm install truffle -g //安装truffle过就请跳过
$ truffle init	//初始化truffle环境
$ npm install @openzeppelin/contracts@2.5.0	//安装openzeppelin合约
$ npm install --save-dev @openzeppelin/test-helpers	//安装openzeppelin测试助手
$ npm install --save-dev @openzeppelin/test-environment mocha chai	//安装openzeppelin //测试环境
$ npm install @truffle/debug-utils

2. 修改package.json

$ vim package.json
// package.json

"scripts": {
  "test": "mocha --exit --recursive",
  "compile": "truffle compile",
  "ganache": "ganache-cli -e 1000",
  "migrate":"truffle migrate"
}

布署一个ERC20合约作为旧合约

1. 新建一个ERC20合约

$ vim contracts/ERC20LegacyToken.sol

合约内容:

pragma solidity >=0.4.21 <0.7.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20Detailed.sol";

contract ERC20LegacyToken is ERC20, ERC20Detailed {
    constructor(
        string memory name,   //代币名称
        string memory symbol, //代币缩写
        uint8 decimals,       //精度
        uint256 totalSupply   //发行总量
    ) public ERC20Detailed(name, symbol, decimals) {
        _mint(msg.sender, totalSupply * (10**uint256(decimals)));
    }
}

2. 编译合约

$ npm run compile

3.布署脚本

$ vim migrations/2_deploy_ERC20LegacyToken.js
const ERC20LegacyToken = artifacts.require("ERC20LegacyToken"); 
module.exports = function(deployer) {
    deployer.deploy(ERC20LegacyToken,
    "My Golden Coin","MGC",18,1000000000);
};

4. 布署合约

$ npm run ganache //创建一个测试节点
$ npm run migrate //在另一个窗口运行

创建并布署迁移合约

1. 创建迁移合约

$ vim contracts/ERC20Migrator.sol
pragma solidity ^0.5.0;
import "@openzeppelin/contracts/drafts/ERC20Migrator.sol";

//代币迁移合约
contract ERC20MigratorContract is ERC20Migrator {
    constructor(
        IERC20 legacyToken    //旧代币合约
    )
        ERC20Migrator(legacyToken)
        public
    {

    }
}

2. 编译合约

$ npm run compile

3. 布署脚本

$ vim migrations/3_deploy_ERC20Migrator.js
const ERC20MigratorContract = artifacts.require("ERC20MigratorContract"); 
const ERC20LegacyToken = artifacts.require("ERC20LegacyToken"); 
module.exports = async function(deployer) {
    const ERC20LegacyTokenInstance = await ERC20LegacyToken.deployed();
    deployer.deploy(
	ERC20MigratorContract,
    	ERC20LegacyTokenInstance.address
    );
};

4. 布署合约

$ npm run migrate //请确保ganache测试节点还在运行

布署新的ERC20合约

1. 新建一个可增发的ERC20合约

$ vim contracts/ERC20NewToken.sol

合约内容:

pragma solidity >=0.4.21 <0.7.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20Detailed.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20Mintable.sol";

contract ERC20NewToken is ERC20, ERC20Detailed, ERC20Mintable {
    constructor(
        string memory name, //代币名称
        string memory symbol, //代币缩写
        uint8 decimals, //精度
        uint256 totalSupply //发行总量
    ) public ERC20Detailed(name, symbol, decimals) {
        _mint(msg.sender, totalSupply * (10**uint256(decimals)));
    }
}

2. 编译合约

$ npm run compile

3.布署脚本

$ vim migrations/4_deploy_ERC20NewToken.js
const ERC20NewToken = artifacts.require("ERC20NewToken"); 
module.exports = function(deployer) {
    deployer.deploy(ERC20NewToken,
    "My Golden Coin","MGC",18,0);//初始发行量为0
};

4. 布署合约

$ npm run migrate //请确保ganache测试节点还在运行

通过测试脚本来模拟运行

1.创建测试脚本

$ vim test/ERC20Migrator.js
const assert = require('assert');
const { contract, accounts, web3 } = require('@openzeppelin/test-environment');
const { ether, time, expectEvent } = require('@openzeppelin/test-helpers');
const ERC20LegacyToken = contract.fromArtifact("ERC20LegacyToken");
const ERC20Migrator = contract.fromArtifact("ERC20MigratorContract");
const ERC20NewToken = contract.fromArtifact("ERC20NewToken");

const totalSupply = '1000000000';//发行总量
[owner, sender, receiver, purchaser, beneficiary] = accounts;
EthValue = '100';
let balanceBefore = [];

//批准和迁移方法
migrateBalance = async (account) => {
    await ERC20Instance.approve(ERC20MigratorInstance.address, balanceBefore[account], { from: account });
    await ERC20MigratorInstance.migrate(account, balanceBefore[account]);
}
//验证余额方法
assertBalanceAfter = async (account) => {
    let balanceAfter = await ERC20NewTokenInstance.balanceOf(account);
    assert.equal(balanceBefore[account].toString(), balanceAfter.toString());
}
//传送方法
transfer = async (sender, receiver, amount) => {
    let receipt = await ERC20Instance.transfer(receiver, ether(amount), { from: sender });
    expectEvent(receipt, 'Transfer', {
        from: sender,
        to: receiver,
        value: ether(amount),
    });
}

describe("布署合约", function () {
    it('布署旧代币合约', async function () {
        ERC20Param = [
            "My Golden Coin",   //代币名称
            "MGC",              //代币缩写
            18,                 //精度
            totalSupply         //发行总量
        ];
        ERC20Instance = await ERC20LegacyToken.new(...ERC20Param, { from: owner });
    });
    it('布署代币迁移合约', async function () {
        ERC20MigratorInstance = await ERC20Migrator.new(
            ERC20Instance.address,        //旧代币合约地址
            { from: owner });
    });
    it('布署新代币合约', async function () {
        ERC20NewTokenParam = [
            "My Golden Coin",   //代币名称
            "MGC",              //代币缩写
            18,                 //精度
            0                   //发行总量
        ];
        ERC20NewTokenInstance = await ERC20NewToken.new(...ERC20NewTokenParam, { from: owner });
    });
});
describe("布署后首先执行", function () {
    it('将代币批准给众筹合约', async function () {
        await ERC20Instance.approve(ERC20MigratorInstance.address, ether(totalSupply.toString()), { from: owner });
    });
    it('添加众筹合约的铸造权: addMinter()', async function () {
        await ERC20NewTokenInstance.addMinter(ERC20MigratorInstance.address, { from: owner });
    });
    it('撤销发送者的铸造权: renounceMinter()', async function () {
        let receipt = await ERC20NewTokenInstance.renounceMinter({ from: owner });
        expectEvent(receipt, 'MinterRemoved', {
            account: owner
        });
    });
});
describe("迁移合约基本信息", function () {
    it('旧合约地址: legacyToken()', async function () {
        assert.equal(ERC20Instance.address, await ERC20MigratorInstance.legacyToken());
    });
});
describe("将旧合约代币分配给一些账户", function () {
    it('代币分配: transfer()', async function () {
        //代币发送给sender
        await transfer(owner, sender, (EthValue * 5).toString());
        //代币发送给receiver
        await transfer(owner, receiver, (EthValue * 10).toString());
        //代币发送给purchaser
        await transfer(owner, purchaser, (EthValue * 15).toString());
        //代币发送给beneficiary
        await transfer(owner, beneficiary, (EthValue * 25).toString());
    });
    it('记录账户旧合约余额: balanceOf()', async function () {
        balanceBefore[owner] = await ERC20Instance.balanceOf(owner);
        balanceBefore[sender] = await ERC20Instance.balanceOf(sender);
        balanceBefore[receiver] = await ERC20Instance.balanceOf(receiver);
        balanceBefore[purchaser] = await ERC20Instance.balanceOf(purchaser);
        balanceBefore[beneficiary] = await ERC20Instance.balanceOf(beneficiary);
    });
});
describe("开始迁移", function () {
    it('开始迁移: beginMigration()', async function () {
        await ERC20MigratorInstance.beginMigration(ERC20NewTokenInstance.address, { from: owner });
    });
    it('验证新约地址: newToken()', async function () {
        assert.equal(ERC20NewTokenInstance.address, await ERC20MigratorInstance.newToken());
    });
    it('迁移owner账户全部余额方法: migrateAll()', async function () {
        await ERC20MigratorInstance.migrateAll(owner);
    });
    it('迁移指定账户余额方法: migrate()', async function () {
        await migrateBalance(sender);
        await migrateBalance(receiver);
        await migrateBalance(purchaser);
        await migrateBalance(beneficiary);
    });
});
describe("验证余额", function () {
    it('验证账户迁移后新合约余额: balanceOf()', async function () {
        await assertBalanceAfter(owner);
        await assertBalanceAfter(sender);
        await assertBalanceAfter(receiver);
        await assertBalanceAfter(purchaser);
        await assertBalanceAfter(beneficiary);
    });
    it('验证账旧ERC20合约余额: balanceOf()', async function () {
        assert.equal('0', (await ERC20Instance.balanceOf(owner)).toString());
        assert.equal('0', (await ERC20Instance.balanceOf(sender)).toString());
        assert.equal('0', (await ERC20Instance.balanceOf(receiver)).toString());
        assert.equal('0', (await ERC20Instance.balanceOf(owner)).toString());
        assert.equal('0', (await ERC20Instance.balanceOf(beneficiary)).toString());
    });
});

2. 运行测试

$ npm run test

3. 运行结果

> truffle@1.0.0 test /home/Documents/truffle
> mocha --exit --recursive



  布署合约
    ✓ 布署旧代币合约 (404ms)
    ✓ 布署代币迁移合约 (47ms)
    ✓ 布署新代币合约 (91ms)

  布署后首先执行
    ✓ 将代币批准给众筹合约 (47ms)
    ✓ 添加众筹合约的铸造权: addMinter()
    ✓ 撤销发送者的铸造权: renounceMinter()

  迁移合约基本信息
    ✓ 旧合约地址: legacyToken()

  将旧合约代币分配给一些账户
    ✓ 代币分配: transfer() (134ms)
    ✓ 记录账户旧合约余额: balanceOf() (92ms)

  开始迁移
    ✓ 开始迁移: beginMigration()
    ✓ 验证新约地址: newToken()
    ✓ 迁移owner账户全部余额方法: migrateAll() (64ms)
    ✓ 迁移指定账户余额方法: migrate() (314ms)

  验证余额
    ✓ 验证账户迁移后新合约余额: balanceOf() (86ms)
    ✓ 验证账旧ERC20合约余额: balanceOf() (82ms)


  15 passing (2s)

在上面这个测试中,我们模拟了一个从旧ERC20代币合约迁移到新合约的全过程. 创建者首先转移了一些数量的代币给了几个用户.随后运行可迁移合约,开始迁移时指定了新合约的地址.当迁移合约布署好之后,持有旧合约代币的用户将余额批准给了迁移合约,然后从迁移合约中运行迁移方法.迁移合约先将旧合约中的批准的代币都转移到自己的账户上.然后再触发新合约中的铸币方法,在新合约中为迁移到用户生成了新的代币.

创建运行环境

1. 首先要初始化环境

$ npm init -y   //初始化npm环境
$ npm install truffle -g //安装truffle过就请跳过
$ truffle init  //初始化truffle环境
$ npm install @openzeppelin/contracts@2.5.0 //安装openzeppelin合约
$ npm install --save-dev @openzeppelin/test-helpers //安装openzeppelin测试助手
$ npm install --save-dev @openzeppelin/test-environment mocha chai  //安装openzeppelin //测试环境
$ npm install @truffle/debug-utils

2. 修改package.json

$ vim package.json
// package.json

"scripts": {
  "test": "mocha --exit --recursive",
  "compile": "truffle compile",
  "ganache": "ganache-cli -e 1000",
  "migrate":"truffle migrate"
}

布署一个ERC20合约作为旧合约

1. 新建一个ERC20合约

$ vim contracts/ERC20LegacyToken.sol

合约内容:

pragma solidity >=0.4.21 <0.7.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20Detailed.sol";

contract ERC20LegacyToken is ERC20, ERC20Detailed {
    constructor(
        string memory name,   //代币名称
        string memory symbol, //代币缩写
        uint8 decimals,       //精度
        uint256 totalSupply   //发行总量
    ) public ERC20Detailed(name, symbol, decimals) {
        _mint(msg.sender, totalSupply * (10**uint256(decimals)));
    }
}

2. 编译合约

$ npm run compile

3.布署脚本

$ vim migrations/2_deploy_ERC20LegacyToken.js
const ERC20LegacyToken = artifacts.require("ERC20LegacyToken"); 
module.exports = function(deployer) {
    deployer.deploy(ERC20LegacyToken,
    "My Golden Coin","MGC",18,1000000000);
};

4. 布署合约

$ npm run ganache //创建一个测试节点
$ npm run migrate //在另一个窗口运行

创建并布署迁移合约

1. 创建迁移合约

$ vim contracts/ERC20Migrator.sol
pragma solidity ^0.5.0;
import "@openzeppelin/contracts/drafts/ERC20Migrator.sol";

//代币迁移合约
contract ERC20MigratorContract is ERC20Migrator {
    constructor(
        IERC20 legacyToken    //旧代币合约
    )
        ERC20Migrator(legacyToken)
        public
    {

    }
}

2. 编译合约

$ npm run compile

3. 布署脚本

$ vim migrations/3_deploy_ERC20Migrator.js
const ERC20MigratorContract = artifacts.require("ERC20MigratorContract"); 
const ERC20LegacyToken = artifacts.require("ERC20LegacyToken"); 
module.exports = async function(deployer) {
    const ERC20LegacyTokenInstance = await ERC20LegacyToken.deployed();
    deployer.deploy(
    ERC20MigratorContract,
        ERC20LegacyTokenInstance.address
    );
};

4. 布署合约

$ npm run migrate //请确保ganache测试节点还在运行

布署新的ERC20合约

1. 新建一个可增发的ERC20合约

$ vim contracts/ERC20NewToken.sol

合约内容:

pragma solidity >=0.4.21 <0.7.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20Detailed.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20Mintable.sol";

contract ERC20NewToken is ERC20, ERC20Detailed, ERC20Mintable {
    constructor(
        string memory name, //代币名称
        string memory symbol, //代币缩写
        uint8 decimals, //精度
        uint256 totalSupply //发行总量
    ) public ERC20Detailed(name, symbol, decimals) {
        _mint(msg.sender, totalSupply * (10**uint256(decimals)));
    }
}

2. 编译合约

$ npm run compile

3.布署脚本

$ vim migrations/4_deploy_ERC20NewToken.js
const ERC20NewToken = artifacts.require("ERC20NewToken"); 
module.exports = function(deployer) {
    deployer.deploy(ERC20NewToken,
    "My Golden Coin","MGC",18,0);//初始发行量为0
};

4. 布署合约

$ npm run migrate //请确保ganache测试节点还在运行

通过测试脚本来模拟运行

1.创建测试脚本

$ vim test/ERC20Migrator.js
const assert = require('assert');
const { contract, accounts, web3 } = require('@openzeppelin/test-environment');
const { ether, time, expectEvent } = require('@openzeppelin/test-helpers');
const ERC20LegacyToken = contract.fromArtifact("ERC20LegacyToken");
const ERC20Migrator = contract.fromArtifact("ERC20MigratorContract");
const ERC20NewToken = contract.fromArtifact("ERC20NewToken");

const totalSupply = '1000000000';//发行总量
[owner, sender, receiver, purchaser, beneficiary] = accounts;
EthValue = '100';
let balanceBefore = [];

//批准和迁移方法
migrateBalance = async (account) => {
    await ERC20Instance.approve(ERC20MigratorInstance.address, balanceBefore[account], { from: account });
    await ERC20MigratorInstance.migrate(account, balanceBefore[account]);
}
//验证余额方法
assertBalanceAfter = async (account) => {
    let balanceAfter = await ERC20NewTokenInstance.balanceOf(account);
    assert.equal(balanceBefore[account].toString(), balanceAfter.toString());
}
//传送方法
transfer = async (sender, receiver, amount) => {
    let receipt = await ERC20Instance.transfer(receiver, ether(amount), { from: sender });
    expectEvent(receipt, 'Transfer', {
        from: sender,
        to: receiver,
        value: ether(amount),
    });
}

describe("布署合约", function () {
    it('布署旧代币合约', async function () {
        ERC20Param = [
            "My Golden Coin",   //代币名称
            "MGC",              //代币缩写
            18,                 //精度
            totalSupply         //发行总量
        ];
        ERC20Instance = await ERC20LegacyToken.new(...ERC20Param, { from: owner });
    });
    it('布署代币迁移合约', async function () {
        ERC20MigratorInstance = await ERC20Migrator.new(
            ERC20Instance.address,        //旧代币合约地址
            { from: owner });
    });
    it('布署新代币合约', async function () {
        ERC20NewTokenParam = [
            "My Golden Coin",   //代币名称
            "MGC",              //代币缩写
            18,                 //精度
            0                   //发行总量
        ];
        ERC20NewTokenInstance = await ERC20NewToken.new(...ERC20NewTokenParam, { from: owner });
    });
});
describe("布署后首先执行", function () {
    it('将代币批准给众筹合约', async function () {
        await ERC20Instance.approve(ERC20MigratorInstance.address, ether(totalSupply.toString()), { from: owner });
    });
    it('添加众筹合约的铸造权: addMinter()', async function () {
        await ERC20NewTokenInstance.addMinter(ERC20MigratorInstance.address, { from: owner });
    });
    it('撤销发送者的铸造权: renounceMinter()', async function () {
        let receipt = await ERC20NewTokenInstance.renounceMinter({ from: owner });
        expectEvent(receipt, 'MinterRemoved', {
            account: owner
        });
    });
});
describe("迁移合约基本信息", function () {
    it('旧合约地址: legacyToken()', async function () {
        assert.equal(ERC20Instance.address, await ERC20MigratorInstance.legacyToken());
    });
});
describe("将旧合约代币分配给一些账户", function () {
    it('代币分配: transfer()', async function () {
        //代币发送给sender
        await transfer(owner, sender, (EthValue * 5).toString());
        //代币发送给receiver
        await transfer(owner, receiver, (EthValue * 10).toString());
        //代币发送给purchaser
        await transfer(owner, purchaser, (EthValue * 15).toString());
        //代币发送给beneficiary
        await transfer(owner, beneficiary, (EthValue * 25).toString());
    });
    it('记录账户旧合约余额: balanceOf()', async function () {
        balanceBefore[owner] = await ERC20Instance.balanceOf(owner);
        balanceBefore[sender] = await ERC20Instance.balanceOf(sender);
        balanceBefore[receiver] = await ERC20Instance.balanceOf(receiver);
        balanceBefore[purchaser] = await ERC20Instance.balanceOf(purchaser);
        balanceBefore[beneficiary] = await ERC20Instance.balanceOf(beneficiary);
    });
});
describe("开始迁移", function () {
    it('开始迁移: beginMigration()', async function () {
        await ERC20MigratorInstance.beginMigration(ERC20NewTokenInstance.address, { from: owner });
    });
    it('验证新约地址: newToken()', async function () {
        assert.equal(ERC20NewTokenInstance.address, await ERC20MigratorInstance.newToken());
    });
    it('迁移owner账户全部余额方法: migrateAll()', async function () {
        await ERC20MigratorInstance.migrateAll(owner);
    });
    it('迁移指定账户余额方法: migrate()', async function () {
        await migrateBalance(sender);
        await migrateBalance(receiver);
        await migrateBalance(purchaser);
        await migrateBalance(beneficiary);
    });
});
describe("验证余额", function () {
    it('验证账户迁移后新合约余额: balanceOf()', async function () {
        await assertBalanceAfter(owner);
        await assertBalanceAfter(sender);
        await assertBalanceAfter(receiver);
        await assertBalanceAfter(purchaser);
        await assertBalanceAfter(beneficiary);
    });
    it('验证账旧ERC20合约余额: balanceOf()', async function () {
        assert.equal('0', (await ERC20Instance.balanceOf(owner)).toString());
        assert.equal('0', (await ERC20Instance.balanceOf(sender)).toString());
        assert.equal('0', (await ERC20Instance.balanceOf(receiver)).toString());
        assert.equal('0', (await ERC20Instance.balanceOf(owner)).toString());
        assert.equal('0', (await ERC20Instance.balanceOf(beneficiary)).toString());
    });
});

2. 运行测试

$ npm run test

3. 运行结果

> truffle@1.0.0 test /home/Documents/truffle
> mocha --exit --recursive

  布署合约
    ✓ 布署旧代币合约 (404ms)
    ✓ 布署代币迁移合约 (47ms)
    ✓ 布署新代币合约 (91ms)

  布署后首先执行
    ✓ 将代币批准给众筹合约 (47ms)
    ✓ 添加众筹合约的铸造权: addMinter()
    ✓ 撤销发送者的铸造权: renounceMinter()

  迁移合约基本信息
    ✓ 旧合约地址: legacyToken()

  将旧合约代币分配给一些账户
    ✓ 代币分配: transfer() (134ms)
    ✓ 记录账户旧合约余额: balanceOf() (92ms)

  开始迁移
    ✓ 开始迁移: beginMigration()
    ✓ 验证新约地址: newToken()
    ✓ 迁移owner账户全部余额方法: migrateAll() (64ms)
    ✓ 迁移指定账户余额方法: migrate() (314ms)

  验证余额
    ✓ 验证账户迁移后新合约余额: balanceOf() (86ms)
    ✓ 验证账旧ERC20合约余额: balanceOf() (82ms)

  15 passing (2s)

在上面这个测试中,我们模拟了一个从旧ERC20代币合约迁移到新合约的全过程. 创建者首先转移了一些数量的代币给了几个用户.随后运行可迁移合约,开始迁移时指定了新合约的地址.当迁移合约布署好之后,持有旧合约代币的用户将余额批准给了迁移合约,然后从迁移合约中运行迁移方法.迁移合约先将旧合约中的批准的代币都转移到自己的账户上.然后再触发新合约中的铸币方法,在新合约中为迁移到用户生成了新的代币.

本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

  • 发表于 5分钟前
  • 阅读 ( 10 )
  • 学分 ( 0 )
  • 分类:智能合约

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

中国机器人

中国机器人

[中]王鸿鹏、[中]马娜 / 辽宁人民出版社 / 2017-1-1 / 48.00元

本书对中国机器人领域的发展历史做了引人入胜的介绍,中国机器人成长的过程也是中国经济由弱到强的历程。本书实际是选择了一个独特的视角来解读中国数十年的政治、经济、国家战略问题。中国的未来充满了多重可能性,本书对想了解中国当代与未来发展战略的读者是难得的读本,对智能制造这一当今世界*受关注的高科技领域在战略层面和科技伦理层面进行了深入地剖析和思考,其中提出的诸多前沿性观点是全球都将面对的问题,对中国科学......一起来看看 《中国机器人》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

html转js在线工具
html转js在线工具

html转js在线工具