How to diagnose an ERC20 error "Error: execution reverted" on a local testnet?
How to diagnose an ERC20 error "Error: execution reverted" on a local testnet?
我有一个本地测试网(由 this Docker image 表示),我正在尝试将一些 ERC20 令牌从一个地址发送到另一个地址。
我为此使用以下 HardHat 脚本:
npx hardhat run --network localTestnet scripts/send-usdt-to-exchange.js
它执行这个文件:
async function main() {
const USDT = await ethers.getContractFactory("USDT");
const usdt = await USDT.attach(
"0xB816192c15160a2C1B4D032CDd7B1009583b21AF"
);
const amount = 1;
const gas = 1000000;
const exchange = "0x190FD61ED8fE0067f0f09EA992C1BF96209bab66";
const usdtSender = "0xDd1e8cC92AF9748193459ADDF910E1b96E88154D";
console.log("Approving the transfer...");
await usdt.approve(usdtSender, amount + gas);
console.log("Done");
console.log("Sending USDT...");
const result = await usdt.transferFrom(usdtSender, exchange, amount, { gasLimit: gas });
console.log("Done, result=", result);
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
执行此脚本几分钟后,我可以在 Blockscout 中看到交易失败并出现 Error: execution reverted
错误。
我在 Raw Trace
下看到 Blockscout 中的一些输出。
[
{
"action": {
"callType": "call",
"from": "0x411167fefecad12da17f9063143706c39528aa28",
"gas": "0xEEC98",
"input": "0x23b872dd000000000000000000000000dd1e8cc92af9748193459addf910e1b96e88154d000000000000000000000000190fd61ed8fe0067f0f09ea992c1bf96209bab660000000000000000000000000000000000000000000000000000000000000001",
"to": "0xb816192c15160a2c1b4d032cdd7b1009583b21af",
"value": "0x0"
},
"error": "execution reverted",
"subtraces": 0,
"traceAddress": [],
"type": "call"
}
]
USDT 合约部署在测试网上使用 this script:
async function main() {
// We get the contract to deploy
const USDT = await ethers.getContractFactory("USDT");
const usdt = await USDT.deploy("0x190FD61ED8fE0067f0f09EA992C1BF96209bab66", "0xDd1e8cC92AF9748193459ADDF910E1b96E88154D");
console.log("USDT contract deployed to:", usdt.address);
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
contract 本身看起来像这样:
pragma solidity ^0.8.0;
import "./tokens/ERC20/ERC20.sol";
contract USDT is ERC20 {
constructor(address exchange, address usdtSender) ERC20("Test Tether", "tUSDT") {
_mint(exchange, 1000000000000000000000);
_mint(usdtSender, 1000000000000000000000);
}
}
鉴于所有这些都发生在本地测试网上,我该如何诊断 execution reverted
错误以及如何修复它?
更新 1: 我注意到一个奇怪的行为。
在执行 send-usdt-to-exchange.js
之前,我通过打开 geth shell 和 运行 以下命令将 ETH 添加到 usdtSender
帐户(0xDd1e8cC92AF9748193459ADDF910E1b96E88154D
):
geth attach http://localhost:8178
web3.personal.unlockAccount('0x411167FeFecAD12Da17F9063143706C39528aa28',
'carsdrivefasterbecausetheyhavebrakes', 600);
eth.sendTransaction({
from: '0x411167FeFecAD12Da17F9063143706C39528aa28',
to: '0xDd1e8cC92AF9748193459ADDF910E1b96E88154D',
value: web3.toWei(1000, 'ether')});
此 returns 交易 ID 0x76794f48a21518a3b3acfe820c2a57f88c188949054573be1805894bb9627471
并且在 BlockScout 中此交易被标记为成功:
Blockscout 中的“原始跟踪”如下所示:
[
{
"action": {
"callType": "call",
"from": "0x411167fefecad12da17f9063143706c39528aa28",
"gas": "0x0",
"input": "0x",
"to": "0xdd1e8cc92af9748193459addf910e1b96e88154d",
"value": "0x3635C9ADC5DEA00000"
},
"result": {
"gasUsed": "0x0",
"output": "0x"
},
"subtraces": 0,
"traceAddress": [],
"type": "call"
}
]
但是,如果我调用
console.log("Determining the ETH balance of USDT sender...");
const ethBalance = await usdt.provider.getBalance(usdtAddress);
console.log("ETH balance:", ethBalance);
console.log("Done");
in send-usdt-to-exchange.js
(交易在 Blockscout 中被标记为成功后)我得到输出
Determining the ETH balance of USDT sender...
ETH balance: BigNumber { _hex: '0x00', _isBigNumber: true }
Done
由于某种原因,即使交易成功,USDT 发送方的 ETH 余额为零。
更新2:geth里面shell,eth.getBalance("0xDd1e8cC92AF9748193459ADDF910E1b96E88154D");
(USDT发送账户的ETH余额)returns2e+21
.
更新 3: 看起来在 Go-Ethereum 的 instructions.go 文件中触发了“执行恢复”错误:
func opRevert(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
offset, size := scope.Stack.pop(), scope.Stack.pop()
ret := scope.Memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64()))
interpreter.returnData = ret
return ret, ErrExecutionReverted
}
这是一个似乎有效的肮脏技巧。
首先,在合约中添加一个转移资金而不改变配额的方法:
pragma solidity ^0.8.0;
import "./tokens/ERC20/ERC20.sol";
contract USDT is ERC20 {
constructor(address exchange, address usdtSender) ERC20("Test Tether", "tUSDT") {
_mint(exchange, 1000000000000000000000);
_mint(usdtSender, 1000000000000000000000);
}
function transferFromWithoutChangingAllowance(
address sender,
address recipient,
uint256 amount
) public returns (bool) {
_transfer(sender, recipient, amount);
return true;
}
}
然后,更改脚本:
async function main() {
const USDT = await ethers.getContractFactory("USDT");
const usdtAddress = "0xa682a5972D1A8175E2431B26586F486bBa161A11";
const usdt = await USDT.attach(usdtAddress);
const amount = 1;
const exchange = "0x190FD61ED8fE0067f0f09EA992C1BF96209bab66";
const usdtSender = "0xDd1e8cC92AF9748193459ADDF910E1b96E88154D";
console.log("Determining the ETH balance of USDT sender...");
const ethBalance = await usdt.provider.getBalance(usdtSender);
console.log("ETH balance:", ethBalance);
console.log("Done");
console.log("Approving the transfer...");
const approveResult = await usdt.approve(usdtSender, amount+1);
console.log("Done, result: ", approveResult);
console.log("Sending USDT...");
const result = await usdt.transferFromWithoutChangingAllowance(usdtSender, exchange, amount, { gasLimit: 1000000 });
console.log("Done, result=", result);
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
我有一个本地测试网(由 this Docker image 表示),我正在尝试将一些 ERC20 令牌从一个地址发送到另一个地址。
我为此使用以下 HardHat 脚本:
npx hardhat run --network localTestnet scripts/send-usdt-to-exchange.js
它执行这个文件:
async function main() {
const USDT = await ethers.getContractFactory("USDT");
const usdt = await USDT.attach(
"0xB816192c15160a2C1B4D032CDd7B1009583b21AF"
);
const amount = 1;
const gas = 1000000;
const exchange = "0x190FD61ED8fE0067f0f09EA992C1BF96209bab66";
const usdtSender = "0xDd1e8cC92AF9748193459ADDF910E1b96E88154D";
console.log("Approving the transfer...");
await usdt.approve(usdtSender, amount + gas);
console.log("Done");
console.log("Sending USDT...");
const result = await usdt.transferFrom(usdtSender, exchange, amount, { gasLimit: gas });
console.log("Done, result=", result);
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
执行此脚本几分钟后,我可以在 Blockscout 中看到交易失败并出现 Error: execution reverted
错误。
我在 Raw Trace
下看到 Blockscout 中的一些输出。
[
{
"action": {
"callType": "call",
"from": "0x411167fefecad12da17f9063143706c39528aa28",
"gas": "0xEEC98",
"input": "0x23b872dd000000000000000000000000dd1e8cc92af9748193459addf910e1b96e88154d000000000000000000000000190fd61ed8fe0067f0f09ea992c1bf96209bab660000000000000000000000000000000000000000000000000000000000000001",
"to": "0xb816192c15160a2c1b4d032cdd7b1009583b21af",
"value": "0x0"
},
"error": "execution reverted",
"subtraces": 0,
"traceAddress": [],
"type": "call"
}
]
USDT 合约部署在测试网上使用 this script:
async function main() {
// We get the contract to deploy
const USDT = await ethers.getContractFactory("USDT");
const usdt = await USDT.deploy("0x190FD61ED8fE0067f0f09EA992C1BF96209bab66", "0xDd1e8cC92AF9748193459ADDF910E1b96E88154D");
console.log("USDT contract deployed to:", usdt.address);
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
contract 本身看起来像这样:
pragma solidity ^0.8.0;
import "./tokens/ERC20/ERC20.sol";
contract USDT is ERC20 {
constructor(address exchange, address usdtSender) ERC20("Test Tether", "tUSDT") {
_mint(exchange, 1000000000000000000000);
_mint(usdtSender, 1000000000000000000000);
}
}
鉴于所有这些都发生在本地测试网上,我该如何诊断 execution reverted
错误以及如何修复它?
更新 1: 我注意到一个奇怪的行为。
在执行 send-usdt-to-exchange.js
之前,我通过打开 geth shell 和 运行 以下命令将 ETH 添加到 usdtSender
帐户(0xDd1e8cC92AF9748193459ADDF910E1b96E88154D
):
geth attach http://localhost:8178
web3.personal.unlockAccount('0x411167FeFecAD12Da17F9063143706C39528aa28',
'carsdrivefasterbecausetheyhavebrakes', 600);
eth.sendTransaction({
from: '0x411167FeFecAD12Da17F9063143706C39528aa28',
to: '0xDd1e8cC92AF9748193459ADDF910E1b96E88154D',
value: web3.toWei(1000, 'ether')});
此 returns 交易 ID 0x76794f48a21518a3b3acfe820c2a57f88c188949054573be1805894bb9627471
并且在 BlockScout 中此交易被标记为成功:
Blockscout 中的“原始跟踪”如下所示:
[
{
"action": {
"callType": "call",
"from": "0x411167fefecad12da17f9063143706c39528aa28",
"gas": "0x0",
"input": "0x",
"to": "0xdd1e8cc92af9748193459addf910e1b96e88154d",
"value": "0x3635C9ADC5DEA00000"
},
"result": {
"gasUsed": "0x0",
"output": "0x"
},
"subtraces": 0,
"traceAddress": [],
"type": "call"
}
]
但是,如果我调用
console.log("Determining the ETH balance of USDT sender...");
const ethBalance = await usdt.provider.getBalance(usdtAddress);
console.log("ETH balance:", ethBalance);
console.log("Done");
in send-usdt-to-exchange.js
(交易在 Blockscout 中被标记为成功后)我得到输出
Determining the ETH balance of USDT sender...
ETH balance: BigNumber { _hex: '0x00', _isBigNumber: true }
Done
由于某种原因,即使交易成功,USDT 发送方的 ETH 余额为零。
更新2:geth里面shell,eth.getBalance("0xDd1e8cC92AF9748193459ADDF910E1b96E88154D");
(USDT发送账户的ETH余额)returns2e+21
.
更新 3: 看起来在 Go-Ethereum 的 instructions.go 文件中触发了“执行恢复”错误:
func opRevert(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
offset, size := scope.Stack.pop(), scope.Stack.pop()
ret := scope.Memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64()))
interpreter.returnData = ret
return ret, ErrExecutionReverted
}
这是一个似乎有效的肮脏技巧。
首先,在合约中添加一个转移资金而不改变配额的方法:
pragma solidity ^0.8.0;
import "./tokens/ERC20/ERC20.sol";
contract USDT is ERC20 {
constructor(address exchange, address usdtSender) ERC20("Test Tether", "tUSDT") {
_mint(exchange, 1000000000000000000000);
_mint(usdtSender, 1000000000000000000000);
}
function transferFromWithoutChangingAllowance(
address sender,
address recipient,
uint256 amount
) public returns (bool) {
_transfer(sender, recipient, amount);
return true;
}
}
然后,更改脚本:
async function main() {
const USDT = await ethers.getContractFactory("USDT");
const usdtAddress = "0xa682a5972D1A8175E2431B26586F486bBa161A11";
const usdt = await USDT.attach(usdtAddress);
const amount = 1;
const exchange = "0x190FD61ED8fE0067f0f09EA992C1BF96209bab66";
const usdtSender = "0xDd1e8cC92AF9748193459ADDF910E1b96E88154D";
console.log("Determining the ETH balance of USDT sender...");
const ethBalance = await usdt.provider.getBalance(usdtSender);
console.log("ETH balance:", ethBalance);
console.log("Done");
console.log("Approving the transfer...");
const approveResult = await usdt.approve(usdtSender, amount+1);
console.log("Done, result: ", approveResult);
console.log("Sending USDT...");
const result = await usdt.transferFromWithoutChangingAllowance(usdtSender, exchange, amount, { gasLimit: 1000000 });
console.log("Done, result=", result);
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});