我正在尝试测试从钱包
receiveInvestment() external payable {
接收货币的函数address private _investorWallet;
是否会更改Solidity合约uint256 private _someStoredValue;
的_dim
值。但是,我可以通过函数调用更改 _someStoredValue;
(并成功断言其副作用),OR 发送传输(并成功断言其副作用)。
我无法两者从钱包
address private _investorWallet
发送资金,和具有接收这些资金的receiveInvestment()
功能,更改_someStoredValue
合约中的_dim
属性和成功断言两个副作用。第一个副作用可以通过验证 _investorWallet
资金被 investmentAmount
减少来断言,第二个副作用可以通过调用 getSomeStoredValue()
合约上的 _dim
函数来断言。
下面是演示该问题的最小工作示例(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
属性已在该函数调用内更改?
我不知道为什么上述解决方案不起作用。然而我犯了很多错误。
_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;
}
}