使用Truffle测试Chainlink ETH转USD功能遇回滚错误求助
解决Chainlink价格喂料测试回滚错误
问题场景
我编写了集成Chainlink AggregatorV3Interface的Bank.sol合约,实现convertToUSD方法将用户ETH余额转换为USD,但用Truffle测试时,存款功能正常,调用convertToUSD却触发回滚错误:
VM Exception while processing transaction: revert
Bank.sol合约代码
// SPDX-License-Identifier: MIT pragma solidity >=0.4.22 <0.9.0; import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; contract Bank is Initializable, AccessControlUpgradeable { // Code to handle two different roles: Manager and User bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE"); bytes32 public constant USER_ROLE = keccak256("USER_ROLE"); mapping(address => uint256) private _balances; // Code for initializer function initialize() public initializer { } function deposit() public payable { _balances[msg.sender] += msg.value; } function convertToUSD(address account) public view returns (uint256) { uint256 ethAmount = _balances[account]; int256 price = getLatestPrice(); uint256 usdAmount = uint256(price) * ethAmount / 1e18; return usdAmount; } function getLatestPrice() public view returns (int256) { AggregatorV3Interface priceFeed = AggregatorV3Interface(0xD4a33860578De61DBAbDc8BFdb98FD742fA7028e); (, int256 price, , , ) = priceFeed.latestRoundData(); return price; } function balanceOf(address account) public view returns (uint256) { return _balances[account]; } function withdraw(uint _amount) public { require (_balances[msg.sender] >= _amount, "Insufficient balance"); _balances[msg.sender] -= _amount; (bool sent,) = msg.sender.call{value: _amount}("sent"); require(sent, "Failed to Complete"); } }
Truffle测试代码
const Bank = artifacts.require('Bank.sol') contract("Bank", async accounts => { it("User should be able to deposit funds", async () => { const bank = await Bank.new() const depositer = accounts[1] const amount = web3.utils.toWei('10', 'ether') await bank.deposit({from: depositer, value: amount}) let balance = await bank.balanceOf(depositer) balance = parseInt(web3.utils.fromWei(balance, 'ether')) assert.equal(balance, 10) let usd = await bank.convertToUSD(depositer) console.log(usd) }) })
问题原因
你在getLatestPrice中使用的地址0xD4a33860578De61DBAbDc8BFdb98FD742fA7028e是以太坊主网的ETH/USD价格喂料合约,但Truffle本地测试链(如Ganache)上不存在这个合约,调用不存在的合约会触发回滚。此外,原合约的initialize函数未调用AccessControl的初始化方法,可能导致潜在的存储问题。
解决方案
1. 修改合约支持可配置价格喂料地址
更新Bank.sol,将价格喂料地址改为可配置,并完成AccessControl的初始化:
// SPDX-License-Identifier: MIT pragma solidity >=0.4.22 <0.9.0; import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; contract Bank is Initializable, AccessControlUpgradeable { bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE"); bytes32 public constant USER_ROLE = keccak256("USER_ROLE"); mapping(address => uint256) private _balances; AggregatorV3Interface public priceFeed; function initialize(address _priceFeed) public initializer { __AccessControl_init(); priceFeed = AggregatorV3Interface(_priceFeed); } function deposit() public payable { _balances[msg.sender] += msg.value; } function convertToUSD(address account) public view returns (uint256) { uint256 ethAmount = _balances[account]; int256 price = getLatestPrice(); uint256 usdAmount = uint256(price) * ethAmount / 1e18; return usdAmount; } function getLatestPrice() public view returns (int256) { (, int256 price, , , ) = priceFeed.latestRoundData(); return price; } function balanceOf(address account) public view returns (uint256) { return _balances[account]; } function withdraw(uint _amount) public { require (_balances[msg.sender] >= _amount, "Insufficient balance"); _balances[msg.sender] -= _amount; (bool sent,) = msg.sender.call{value: _amount}(""); require(sent, "Failed to Complete"); } }
2. 编写模拟价格喂料合约
创建MockAggregator.sol,用于本地测试时返回固定价格:
// SPDX-License-Identifier: MIT pragma solidity >=0.4.22 <0.9.0; import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; contract MockAggregator is AggregatorV3Interface { int256 public latestPrice; constructor(int256 _price) { latestPrice = _price; } function latestRoundData() external view returns ( uint80, int256, uint256, uint256, uint80 ) { return (0, latestPrice, 0, 0, 0); } function decimals() external pure returns (uint8) { return 8; } // 实现其他未使用的接口方法 function description() external pure returns (string memory) { return "Mock ETH/USD Price Feed"; } function version() external pure returns (uint256) { return 1; } function getRoundData(uint80) external view returns ( uint80, int256, uint256, uint256, uint80 ) { return (0, latestPrice, 0, 0, 0); } }
3. 更新测试代码
修改Truffle测试,先部署模拟喂料合约,再初始化Bank合约:
const Bank = artifacts.require('Bank') const MockAggregator = artifacts.require('MockAggregator') contract("Bank", async accounts => { it("User should deposit funds and convert balance to USD", async () => { // 模拟ETH价格为2000 USD(Chainlink价格喂料返回8位小数,所以是2000 * 1e8 = 200000000000) const mockPrice = web3.utils.toBN('200000000000') const mockAggregator = await MockAggregator.new(mockPrice) // 初始化Bank合约,传入模拟喂料地址 const bank = await Bank.new() await bank.initialize(mockAggregator.address) const depositer = accounts[1] const amount = web3.utils.toWei('10', 'ether') await bank.deposit({from: depositer, value: amount}) // 验证ETH余额 let balance = await bank.balanceOf(depositer) balance = parseInt(web3.utils.fromWei(balance, 'ether')) assert.equal(balance, 10, "Deposit amount mismatch") // 验证USD转换结果 let usd = await bank.convertToUSD(depositer) // 预期值:10 ETH * 2000 USD/ETH = 20000 USD(结果为20000 * 1e8 = 2000000000000) const expectedUsd = web3.utils.toBN('2000000000000') assert.equal(usd.toString(), expectedUsd.toString(), "USD conversion mismatch") console.log(`USD value: ${web3.utils.fromWei(usd, 'gwei')} USD`) }) })
额外注意事项
- 原合约的withdraw函数中,
call的参数"sent"是多余的,会导致发送ETH时附带无用数据,已修改为空字符串。 - 部署到主网时,只需传入主网的Chainlink价格喂料地址即可正常使用。
内容的提问来源于stack exchange,提问作者Darryl Soh Soon Yong
相关产品推荐
相关产品推荐

