首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Polygon 升级与 EIP-7702 的引入的一个问题

Polygon 升级与 EIP-7702 的引入的一个问题

原创
作者头像
rectinajh
发布2025-08-20 17:40:55
发布2025-08-20 17:40:55
1930
举报

Polygon 升级的背景与 EIP-7702 的引入

Polygon 网络(原 Matic Network)自 2021 年以来不断演进,2025 年 7 月的 Bhilai Hardfork 是其关键里程碑。这一升级整合了 Ethereum 的 Pectra 升级元素,包括 EIP-7702、EIP-7594 等提案,旨在优化网络性能、降低 gas 费用,并提升账户灵活性。根据 Polygon 官方文档,Bhilai Hardfork 于 2025 年 7 月 10 日激活,区块高度约 75,000,000 左右。这次升级使 Polygon 支持 Type 4 交易类型,即 EIP-7702 交易。

EIP-7702 是 Ethereum 社区在 2024 年提出的提案,由 Vitalik Buterin 等核心开发者推动。它允许 EOA 在交易期间临时 “借用” 合约代码执行复杂操作,如批量调用或代币批准,而无需将 EOA 转换为智能合约账户。这降低了部署成本,提高了用户体验,尤其适用于智能钱包(如 Coinbase Wallet 或 Safe)。在 Polygon 上,EIP-7702 的支持意味着开发者可以创建更先进的 DApp,例如赞助交易(relayer 支付 gas 费用)或账户抽象操作。

然而,这一升级并非一帆风顺。它引入了新交易类型和严格的验证规则,导致原有工具和代码需要调整。用户在尝试复制旧交易(如基于 ERC-4337 的账户抽象交易)时,经常遇到兼容性问题。这正是您在对话中遇到的错误源头:从 “invalid BytesLike value” 到 “bad address checksum” 和 “invalid address (value=null)”。

但兴奋没持续多久,就撞上了铁壁。问题是这样的:我试图复制一个成功的EIP-7702交易(哈希0x5e7995f5c313ec57e006f41cd3f236733563ba720b17cf84da8f840df7aeff95),

https://polygonscan.com/tx/0x5e7995f5c313ec57e006f41cd3f236733563ba720b17cf84da8f840df7aeff95

这是一个批准USDT权限的操作,使用relayer执行批量调用。目标是让我的Spender地址0xE3FBCE7898d7b92591730cDD63A859F90F89D110代表Owner地址0xD88dcCccD6e1c614614e21183c97e5391718d929无限花费USDT。我用ethers.js v6.15.0和Remix构建脚本,一切看起来完美。但运行sendEIP7702Tx.js时,错误如洪水般涌来:先是“invalid BytesLike value”,然后“bad address checksum”,最后“invalid address (value=null)”。这些错误像链条一样缠绕。

作为密码朋克,我不相信权威的“升级”,我相信代码的真相。问题出现于Polygon升级后:Bhilai Hardfork激活了EIP-7702,让网络支持Type 4交易,但这也引入了更严格的验证机制。ethers.js开始强制执行EIP-55校验和规则,地址必须是混合大小写的完美格式,否则就报“bad address checksum”。我的目标地址0xd88DcCcCD6e1C614614e21183c97e5391718D929看起来合法,但大小写不匹配哈希计算,导致验证失败。这不是bug,这是升级为安全设计的:防止地址输入错误,避免资金丢失。但对我们极客来说,这是对旧代码的宣战——全小写地址突然成了叛徒。

更深层的问题是“invalid BytesLike value”。EIP-7702要求contractCode(临时合约字节码)是有效的BytesLike值,必须以“0x”开头,并通过ethers.getBytes转换。但我的字节码从Remix复制时缺少前缀,或包含隐形字符,导致解析失败。升级后,ethers.js对字节码的检查更严苛,以防恶意代码注入。这体现了密码朋克精神:安全第一,但也增加了调试的痛苦。

最棘手的“invalid address (value=null)”则指向authorizationList。EIP-7702的授权对象在ethers.js中必须包含address字段(授权的目标地址),如果缺失,内部验证会视其为null,导致错误。这不是EIP-7702规范的要求,而是ethers.js的实现细节——升级后,库加强了授权验证,以防止重放攻击。签名格式也需v、r、s,而非yParity,否则转换失败。这些错误串联起来:从字节码格式,到地址校验,到授权字段缺失,全是升级引入的新标准导致的兼容断层。

分析这些问题,我看到Polygon升级的双刃剑。Bhilai Hardfork整合了Ethereum的Pectra升级,提升了网络性能和账户灵活性,但牺牲了向后兼容。EIP-7702本是为自由设计的:EOA可以临时执行代码,如批量批准代币,而relayer支付gas,实现赞助交易。这对隐私至关重要——不再需要暴露主账户。但升级后,工具生态跟不上:Remix不支持Type 4,ethers.js验证更严,旧代码崩盘。这让我回想起密码朋克的起源:Cypherpunks write code。我们必须自己破解,而不是等待中心化实体修复。

解决过程像一场黑客马拉松。首先,修复字节码:从Remix复制运行时object字段,确保以“0x”开头,并用ethers.getBytes(contractCode)转换。运行signAuthorization.js生成签名时,也应用相同字节码,避免不匹配。Nonce设为126(当前125的下一个),防止重放。

合约代码

代码语言:js
复制
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract EIP7702Executor {
    struct Call {
        address target;
        uint256 value;
        bytes data;
    }

    function executeBatch(Call[] calldata calls) external {
        for (uint256 i = 0; i < calls.length; i) {
            (bool success, ) = calls[i].target.call{value: calls[i].value}(calls[i].data);
            require(success, "Call failed");
            unchecked { i++; }
        }
    }
}

其次,处理校验和:用ethers.getAddress转换所有地址,如targetAddress = ethers.getAddress('0xd88dccccd6e1c614614e21183c97e5391718d929'),输出正确的0xD88dcCccD6e1c614614e21183c97e5391718d929。更新to字段和data中的嵌入地址(如USDT和Spender)为校验和格式。

最后,补全authorizationList:添加address字段,值为to地址,并将yParity转换为v = Number(yParity) + 27(例如0x0 -> 27)。签名用v, r, s格式。更新sendEIP7702Tx.js后,运行成功,交易哈希0x0351e55499007b8139635950f4a01878845fc5ef9e336e9cc703f05112e9ab7c确认。

signAuthorization.js

代码语言:js
复制
const ethers = require('ethers');

// 目标地址的私钥(0xd88DcCcCD6e1C614614e21183c97e5391718D929)
const privateKey = ''; // 替换为实际私钥,切勿公开
const wallet = new ethers.Wallet(privateKey);

// 合约字节码(从 Remix 复制)
const contractBytecode = '0x608060405234801561000f575f80fd5b506104a18061001d5f395ff3fe608060405234801561000f575f80fd5b5060043610610029575f3560e01c806334fcd5be1461002d575b5f80fd5b6100476004803603810190610042919061020d565b610049565b005b5f5b8282905081101561019f575f83838381811061006a57610069610258565b5b905060200281019061007c9190610291565b5f01602081019061008d9190610312565b73ffffffffffffffffffffffffffffffffffffffff168484848181106100b6576100b5610258565b5b90506020028101906100c89190610291565b602001358585858181106100df576100de610258565b5b90506020028101906100f19190610291565b8060400190610100919061033d565b60405161010e9291906103db565b5f6040518083038185875af1925050503d805f8114610148576040519150601f19603f3d011682016040523d82523d5f602084013e61014d565b606091505b5050905080610191576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101889061044d565b60405180910390fd5b81806001019250505061004b565b505050565b5f80fd5b5f80fd5b5f80fd5b5f80fd5b5f80fd5b5f8083601f8401126101cd576101cc6101ac565b5b8235905067ffffffffffffffff8111156101ea576101e96101b0565b5b602083019150836020820283011115610206576102056101b4565b5b9250929050565b5f8060208385031215610223576102226101a4565b5b5f83013567ffffffffffffffff8111156102405761023f6101a8565b5b61024c858286016101b8565b92509250509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f80fd5b5f80fd5b5f80fd5b5f823560016060038336030381126102ac576102ab610285565b5b80830191505092915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6102e1826102b8565b9050919050565b6102f1816102d7565b81146102fb575f80fd5b50565b5f8135905061030c816102e8565b92915050565b5f60208284031215610327576103266101a4565b5b5f610334848285016102fe565b91505092915050565b5f808335600160200384360303811261035957610358610285565b5b80840192508235915067ffffffffffffffff82111561037b5761037a610289565b5b6020830192506001820236038313156103975761039661028d565b5b509250929050565b5f81905092915050565b828183375f83830152505050565b5f6103c2838561039f565b93506103cf8385846103a9565b82840190509392505050565b5f6103e78284866103b7565b91508190509392505050565b5f82825260208201905092915050565b7f43616c6c206661696c65640000000000000000000000000000000000000000005f82015250565b5f610437600b836103f3565b915061044282610403565b602082019050919050565b5f6020820190508181035f8301526104648161042b565b905091905056fea26469706673582212207b699850cde1f81d5a2400678982028b07b490c46bea5d83d2b544c7661efb6964736f6c63430008140033'; // 替换为 EIP7702Executor.sol 的字节码

// Nonce(基于您提供的结果,使用 126,因为 125 已用于之前的签名)
const nonce = 126;

// 创建授权数据
const authorization = {
    contractCode: contractBytecode,
    nonce: nonce,
};

// 编码并签名
async function signAuthorization() {
    const domain = {
        name: 'EIP7702',
        version: '1',
        chainId: 137, // Polygon 主网
        verifyingContract: '0x0000000000000000000000000000000000000000',
    };
    const types = {
        Authorization: [
            { name: 'contractCode', type: 'bytes' },
            { name: 'nonce', type: 'uint256' },
        ],
    };
    const signature = await wallet.signTypedData(domain, types, authorization);
    console.log('Signature:', signature);

    // 拆分签名
    const sig = ethers.Signature.from(signature);
    console.log('yParity:', sig.v === 27 ? '0x0' : '0x1');
    console.log('r:', sig.r);
    console.log('s:', sig.s);
}

signAuthorization();

encodeInputData.js

代码语言:js
复制
const { ethers } = require('ethers');

// Define the ABI for the executeBatch function
const abi = ['function executeBatch(tuple(address target, uint256 value, bytes data)[] calls)'];
const iface = new ethers.Interface(abi);

// Encode the approve call data
const usdtAbi = ['function approve(address spender, uint256 value)'];
const usdtInterface = new ethers.Interface(usdtAbi);
const approveData = usdtInterface.encodeFunctionData('approve', [
    '0xE3FBCE7898d7b92591730cDD63A859F90F89D110', // Spender address
    '115792089237316195423570985008687907853269984665640564039457584007913129639935' // Max uint256
]);

// Define the calls array
const calls = [
    {
        target: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F', // USDT contract
        value: 0,
        data: ethers.getBytes(approveData) // Ensure BytesLike format
    }
];

// Encode the executeBatch call
const inputData = iface.encodeFunctionData('executeBatch', [calls]);
console.log('Input Data:', inputData);

sendEIP7702Tx.js

代码语言:js
复制
const { ethers } = require('ethers');

async function sendEIP7702Tx() {
    // 连接 Polygon 主网
    const provider = new ethers.JsonRpcProvider('https://polygon-bor-rpc.publicnode.com');
    const wallet = new ethers.Wallet('', provider); // 0xE3FBCE7898d7b92591730cDD63A859F90F89D110 的私钥

    // 修复地址校验和问题
    const privateKey = '';
    const wallets = new ethers.Wallet(privateKey);
    console.log('Address:', wallets.address);

    const nonce = await provider.getTransactionCount(wallets.address, 'latest')
    console.log('nonce:', nonce);

    // 交易参数
    const tx = {
        type: 4, // EIP-7702
        to:  wallets.address,
        value: 0,
        data: '0x34fcd5be000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000c2132d05d31c914a87c6611c10748aeb04b58e8f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000e3fbce7898d7b92591730cdd63a859f90f89d110ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000', // 从 encodeInputData.js
        gasLimit: 300000,
        gasPrice: ethers.parseUnits('26', 'gwei'),
        // nonce: 126,
        chainId: 137,
        authorizationList: [
            {
                address:  '0xE3FBCE7898d7b92591730cDD63A859F90F89D110', // 必需字段:授权的目标地址 (EIP7702Executor合约地址)
                code: ethers.getBytes('0x608060405234801561000f575f80fd5b506104a18061001d5f395ff3fe608060405234801561000f575f80fd5b5060043610610029575f3560e01c806334fcd5be1461002d575b5f80fd5b6100476004803603810190610042919061020d565b610049565b005b5f5b8282905081101561019f575f83838381811061006a57610069610258565b5b905060200281019061007c9190610291565b5f01602081019061008d9190610312565b73ffffffffffffffffffffffffffffffffffffffff168484848181106100b6576100b5610258565b5b90506020028101906100c89190610291565b602001358585858181106100df576100de610258565b5b90506020028101906100f19190610291565b8060400190610100919061033d565b60405161010e9291906103db565b5f6040518083038185875af1925050503d805f8114610148576040519150601f19603f3d011682016040523d82523d5f602084013e61014d565b606091505b5050905080610191576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101889061044d565b60405180910390fd5b81806001019250505061004b565b505050565b5f80fd5b5f80fd5b5f80fd5b5f80fd5b5f80fd5b5f8083601f8401126101cd576101cc6101ac565b5b8235905067ffffffffffffffff8111156101ea576101e96101b0565b5b602083019150836020820283011115610206576102056101b4565b5b9250929050565b5f8060208385031215610223576102226101a4565b5b5f83013567ffffffffffffffff8111156102405761023f6101a8565b5b61024c858286016101b8565b92509250509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f80fd5b5f80fd5b5f80fd5b5f823560016060038336030381126102ac576102ab610285565b5b80830191505092915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6102e1826102b8565b9050919050565b6102f1816102d7565b81146102fb575f80fd5b50565b5f8135905061030c816102e8565b92915050565b5f60208284031215610327576103266101a4565b5b5f610334848285016102fe565b91505092915050565b5f808335600160200384360303811261035957610358610285565b5b80840192508235915067ffffffffffffffff82111561037b5761037a610289565b5b6020830192506001820236038313156103975761039661028d565b5b509250929050565b5f81905092915050565b828183375f83830152505050565b5f6103c2838561039f565b93506103cf8385846103a9565b82840190509392505050565b5f6103e78284866103b7565b91508190509392505050565b5f82825260208201905092915050565b7f43616c6c206661696c65640000000000000000000000000000000000000000005f82015250565b5f610437600b836103f3565b915061044282610403565b602082019050919050565b5f6020820190508181035f8301526104648161042b565b905091905056fea26469706673582212207b699850cde1f81d5a2400678982028b07b490c46bea5d83d2b544c7661efb6964736f6c63430008140033'), // 确保包含 0x 前缀
                nonce: nonce,
                v: 27, // yParity 是 '0x0',所以 v = 27
                r: '0x64f64fec2f1adbde8e0282f18e9d95b917f45735d3a9dcf69de5827fa867872a',
                s: '0x501b7e02202fcd6c65f1b5f7980f0592241e8f5268d23c8bba738854d8bd66dd'
            }
        ]
    };

    // 发送交易
    const txResponse = await wallet.sendTransaction(tx);
    console.log('Transaction Hash:', txResponse.hash);
    await txResponse.wait();
    console.log('Transaction Confirmed');
}

sendEIP7702Tx().catch(console.error);

https://polygonscan.com/tx/0xbef0e3a909e208c5494509ddf7946a6e7e6a5547291a4c2b9e1d91bfdc69a531

整个过程让我体会到密码朋克的真谛:问题即机会。通过开源工具和社区文档,我破解了升级的陷阱。现在,我可以用Spender调用transferFrom转移USDT,实现了真正去中心化的控制。

transferFrom.js

代码语言:js
复制
const { ethers } = require('ethers');

async function transferFromUSDT() {
    // 连接 Polygon 主网
    const provider = new ethers.JsonRpcProvider('https://polygon-bor-rpc.publicnode.com');
    const privateKey = ''; // Spender 私钥 (0xE3FBCE7898d7b92591730cDD63A859F90F89D110)
    const wallet = new ethers.Wallet(privateKey, provider);

    // USDT 合约地址和 ABI
    const usdtAddress = '0xc2132D05D31c914a87C6611C10748AEb04B58e8F';
    const usdtAbi = [
        'function balanceOf(address owner) view returns (uint256)',
        'function transferFrom(address from, address to, uint256 value) returns (bool)'
      ];
    const usdtContract = new ethers.Contract(usdtAddress, usdtAbi, wallet);

    // 转移参数
    // const wallet = new ethers.Wallet('', provider); // 0xE3FBCE7898d7b92591730cDD63A859F90F89D110 的私钥

    // 修复地址校验和问题
    const privateKey2 = '0x07288a0740191b921bff404ea2294a87afad9377180c79ea192a81f98def5d5d';
    const wallets = new ethers.Wallet(privateKey2);
    console.log('Address:', wallets.address);

    const fromAddress = wallets.address; // Owner 地址
    const toAddress = wallet.address; // 替换为您希望接收 USDT 的地址
    const amount = ethers.parseUnits('191.4', 6); // 转移 100 USDT (6 位小数)

    // 检查余额
    const balance = await usdtContract.balanceOf(fromAddress);
    console.log(`Owner USDT Balance: ${ethers.formatUnits(balance, 6)} USDT`);

    if (balance < amount) {
        throw new Error('Insufficient USDT balance in Owner address');
    }

    // 调用 transferFrom
    const tx = await usdtContract.transferFrom(fromAddress, toAddress, amount, {
        gasLimit: 100000,
        gasPrice: ethers.parseUnits('26', 'gwei')
    });

    console.log('Transaction Hash:', tx.hash);
    await tx.wait();
    console.log('Transfer Confirmed');
}

transferFromUSDT().catch(console.error);

Polygon升级是进步,但它提醒我们:自由不是免费的。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档