交易后断言属性更改,使用 Foundry 测试网络

问题描述 投票:0回答:1

背景

我正在尝试测试从钱包

receiveInvestment() external payable {
接收货币的函数
address private _investorWallet;
是否会更改Solidity合约
uint256 private _someStoredValue;
_dim
值。但是,我可以通过函数调用更改
_someStoredValue;
(并成功断言其副作用),OR 发送传输(并成功断言其副作用)。

问题

我无法两者从钱包

address private _investorWallet
发送资金,具有接收这些资金的
receiveInvestment()
功能,更改
_someStoredValue
合约中的
_dim
属性成功断言两个副作用。第一个副作用可以通过验证
_investorWallet
资金被
investmentAmount
减少来断言,第二个副作用可以通过调用
getSomeStoredValue()
合约上的
_dim
函数来断言。

MWE

下面是演示该问题的最小工作示例(MWE),它由带有 Soldity 代码的

_dim
合约组成:

// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.23; // Specifies the Solidity compiler version.

import { console2 } from "forge-std/src/console2.sol";

contract DecentralisedInvestmentManager {
  event PaymentReceived(address from, uint256 amount);
  event InvestmentReceived(address from, uint256 amount);

  uint256 private _projectLeadFracNumerator;
  uint256 private _projectLeadFracDenominator;
  address private _projectLead;

  uint256 private _someStoredValue;

  /**
   * Constructor for creating a Tier instance. The values cannot be changed
   * after creation.
   *
   */
  constructor(uint256 projectLeadFracNumerator, uint256 projectLeadFracDenominator, address projectLead) {
    // Store incoming arguments in contract.
    _projectLeadFracNumerator = projectLeadFracNumerator;
    _projectLeadFracDenominator = projectLeadFracDenominator;
    _projectLead = projectLead;
  }

  function receiveInvestment() external payable {
    require(msg.value > 0, "The amount invested was not larger than 0.");

    require(msg.sender == 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496, "The sender was unexpected.");

    _someStoredValue = 15;

    emit InvestmentReceived(msg.sender, msg.value);
  }

  function getSomeStoredValue() public view returns (uint256) {
    return _someStoredValue;
  }
}

测试文件:

// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.23 <0.9.0;
import { console2 } from "forge-std/src/console2.sol";

// Used to run the tests
import { PRBTest } from "@prb/test/src/PRBTest.sol";
import { StdCheats } from "forge-std/src/StdCheats.sol";

// Import the main contract that is being tested.
import { DecentralisedInvestmentManager } from "../src/DecentralisedInvestmentManager.sol";

/// @dev If this is your first time with Forge, read this tutorial in the Foundry Book:
/// https://book.getfoundry.sh/forge/writing-tests
contract SimplifiedTest is PRBTest, StdCheats {
  address internal projectLeadAddress;
  address private _investorWallet;
  address private _userWallet;
  DecentralisedInvestmentManager private _dim;

  /// @dev A function invoked before each test case is run.
  function setUp() public virtual {
    // Initialise the main contract that is being tested.
    projectLeadAddress = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266;
    uint256 projectLeadFracNumerator = 4;
    uint256 projectLeadFracDenominator = 10;
    _dim = new DecentralisedInvestmentManager(projectLeadFracNumerator, projectLeadFracDenominator, projectLeadAddress);

    // Initialsie the investor and user wallets used to interact with the main contract.
    _investorWallet = address(uint160(uint256(keccak256(bytes("1")))));
    deal(_investorWallet, 80000 wei);
    _userWallet = address(uint160(uint256(keccak256(bytes("2")))));
    deal(_userWallet, 100002 wei);

    // Print the addresses to console.
    console2.log("projectLeadAddress=    ", projectLeadAddress);
    console2.log("_investorWallet=       ", _investorWallet);
    console2.log("_userWallet=           ", _investorWallet, "\n");
  }

  /**
  Test whether the _someStoredValue attribute in the _dim contract is saved
  after its value is set in the receiveInvestment function.*/
  function testDimAttributeIsSaved() public {
    uint256 startBalance = _investorWallet.balance;
    uint256 investmentAmount = 200_000 wei;

    // Send investment directly from the investor wallet.
    (bool investmentSuccess, bytes memory investmentResult) = _investorWallet.call{ value: investmentAmount }(
      abi.encodeWithSelector(_dim.receiveInvestment.selector)
    );
    // (bool success, ) = address(_dim).call{value: investmentAmount}(abi.encodeWithSignature("receiveInvestment()"));

    // Assert that investor balance decreased by the investment amount.
    uint256 endBalance = _investorWallet.balance;
    assertEq(endBalance - startBalance, investmentAmount);

    // Assert investment data is stored in contract object _dim.
    // FAILS:
    assertEq(_dim.getSomeStoredValue(), 15);
  }
}

有问题的行在测试文件中:

(bool investmentSuccess, bytes memory investmentResult) = _investorWallet.call{ value: investmentAmount }(
      abi.encodeWithSelector(_dim.receiveInvestment.selector)
    );

该交易已注册在

_investorWallet
的余额中,但
_someStoredValue
返回的
getSomeStoredValue()
是 0 而不是 15,这意味着
receiveInvestment
函数执行的属性更改不会存储/见证测试文件中的
_dim
合约。

为了方便起见,您还可以签出 this 提交并运行:

forge test -vvv --match-test testDimAttributeIsSaved

重现错误。

输出

运行上面的 MWE 会产生输出:

[⠊] Compiling...
[⠆] Compiling 2 files with 0.8.23
[⠰] Solc 0.8.23 finished in 223.55ms
Compiler run successful with warnings:
Warning (2072): Unused local variable.
  --> test/simplify.t.sol:48:6:
   |
48 |     (bool investmentSuccess, bytes memory investmentResult) = _investorWallet.call{ value: investmentAmount }(
   |      ^^^^^^^^^^^^^^^^^^^^^^

Warning (2072): Unused local variable.
  --> test/simplify.t.sol:48:30:
   |
48 |     (bool investmentSuccess, bytes memory investmentResult) = _investorWallet.call{ value: investmentAmount }(
   |                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


Ran 1 test for test/simplify.t.sol:SimplifiedTest
[FAIL. Reason: assertion failed] testDimAttributeIsSaved() (gas: 51284)
Logs:
  projectLeadAddress=     0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
  _investorWallet=        0x82Df0950F5A951637E0307CdCB4c672F298B8Bc6
  _userWallet=            0x82Df0950F5A951637E0307CdCB4c672F298B8Bc6 


Traces:
  [51284] SimplifiedTest::testDimAttributeIsSaved()
    ├─ [0] 0x82Df0950F5A951637E0307CdCB4c672F298B8Bc6::receiveInvestment{value: 200000}()
    │   └─ ← [Stop] 
    ├─ [2268] DecentralisedInvestmentManager::getSomeStoredValue() [staticcall]
    │   └─ ← [Return] 0
    ├─ emit Log(err: "Error: a == b not satisfied [uint256]")
    ├─ emit LogNamedUint256(key: "   Left", value: 0)
    ├─ emit LogNamedUint256(key: "  Right", value: 15)
    ├─ [0] VM::store(VM: [0x7109709ECfa91a80626fF3989D68f67F5b1DD12D], 0x6661696c65640000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000001)
    │   └─ ← [Return] 
    └─ ← [Stop] 

Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 286.63µs (38.54µs CPU time)

Ran 1 test suite in 531.22ms (286.63µs CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)

Failing tests:
Encountered 1 failing test in test/simplify.t.sol:SimplifiedTest
[FAIL. Reason: assertion failed] testDimAttributeIsSaved() (gas: 51284)

Encountered a total of 1 failing tests, 0 tests succeeded

这意味着

_someStoredValue
是 0 而不是 15,尽管人们期望
receiveInvestment
函数已将其设置为 15。

问题

如何从钱包

_investorWallet
执行一笔交易到
receiveInvestment
并断言
_dim._someStoredValue
属性已在该函数调用内更改?

unit-testing testing solidity
1个回答
0
投票

错误

我不知道为什么上述解决方案不起作用。然而我犯了很多错误。

  • 我试图发送比投资者钱包包含的更多的 eth。
  • 当交易不起作用时,我(错误地)假设交易是从投资者钱包以外的钱包发送的。
  • 我(错误地)假设传输中的
    _dim
    实例是另一个
    _dim
    实例,而不是我在有关属性的断言中使用的实例。

猜问题

我认为这句话:

 // Send investment directly from the investor wallet.
    (bool investmentSuccess, bytes memory investmentResult) = _investorWallet.call{ value: investmentAmount }(
      abi.encodeWithSelector(_dim.receiveInvestment.selector)
    );

尝试从

receiveInvestment
而不是从
investorWallet
合约调用
_dim
函数。然而,这似乎与关于
investorWallet
的观察相矛盾,它失去了投资金额,这似乎意味着交易成功,即使在以太坊中,如果交易被定向到不可支付的函数或构造函数,交易也会被拒绝。我不知道这笔交易最终会在哪里结束,但我认为这不太可能是一个付费功能(尽管我认为证据与此相矛盾,除非
investmentAmount
花在了汽油上。)。

解决方案

下面的解决方案首先将消息发送者设置为

investorWallet
地址,然后执行转入
_dim.receiveInvestment
函数。本次交易后属性已成功更改。

/**
  Test whether the _someStoredValue attribute in the _dim contract is saved
  after its value is set in the receiveInvestment function.*/
  function testPaymentTriggersAttributeChange() public {
    uint256 startBalance = _investorWallet.balance;
    uint256 investmentAmount = 200_000 wei;

    console2.log("_dim balance before=", address(_dim).balance);
    vm.deal(address(_investorWallet), startBalance);
    vm.prank(address(_investorWallet));
    _dim.receiveInvestment{ value: investmentAmount }();

    // require(success, "Send ether failed");

    // Call receiveInvestment directly on _dim
    // _dim.receiveInvestment{value: investmentAmount}();
    // bytes memory callData = abi.encodeWithSelector(_dim.receiveInvestment.selector, investmentAmount);

    // Assert that investor balance decreased by the investment amount
    uint256 endBalance = _investorWallet.balance;

    console2.log("startBalance=", startBalance);
    console2.log("endBalance=", endBalance);
    console2.log("investmentAmount=", investmentAmount);
    console2.log("_dim balance after=", address(_dim).balance);

    assertEq(startBalance - endBalance, investmentAmount);

    // Assert balance in dim contract equals the investmentAmount.
    assertEq(investmentAmount, address(_dim).balance);

    // Assert investment data is stored in contract object _dim.
    assertEq(_dim.getSomeStoredValue(), 15);
  }

通过接收付款的基础合约:

// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.23; // Specifies the Solidity compiler version.

import { console2 } from "forge-std/src/console2.sol";

contract DecentralisedInvestmentManager {
  event PaymentReceived(address from, uint256 amount);
  event InvestmentReceived(address from, uint256 amount);

  uint256 private _projectLeadFracNumerator;
  uint256 private _projectLeadFracDenominator;
  address private _projectLead;

  uint256 public _someStoredValue;

  /**
   * Constructor for creating a Tier instance. The values cannot be changed
   * after creation.
   *
   */
  constructor(uint256 projectLeadFracNumerator, uint256 projectLeadFracDenominator, address projectLead) {
    // Store incoming arguments in contract.
    _projectLeadFracNumerator = projectLeadFracNumerator;
    _projectLeadFracDenominator = projectLeadFracDenominator;
    _projectLead = projectLead;
  }

  function receiveInvestment() external payable {

    require(msg.value > 0, "The amount invested was not larger than 0.");

    require(msg.sender == 0x82Df0950F5A951637E0307CdCB4c672F298B8Bc6, "The sender was unexpected.");

    _someStoredValue = 15;
    console2.log("Set _someStoredValue to:", _someStoredValue);

    emit InvestmentReceived(msg.sender, msg.value);
  }

  function getSomeStoredValue() public view returns (uint256) {
    return _someStoredValue;
  }
}

© www.soinside.com 2019 - 2024. All rights reserved.