-
Notifications
You must be signed in to change notification settings - Fork 6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[isoltest] Add support for events. #10728
Conversation
bf5bace
to
581afdd
Compare
How do you imagine such a test file to look in the end? I think it is not too important to support logs that are not produced by events. |
Given this BOOST_AUTO_TEST_CASE(event_emit)
{
char const* sourceCode = R"(
contract ClientReceipt {
event Deposit(address indexed _from, bytes32 indexed _id, uint _value);
function deposit(bytes32 _id) public payable {
emit Deposit(msg.sender, _id, msg.value);
}
}
)";
ALSO_VIA_YUL(
DISABLE_EWASM_TESTRUN()
compileAndRun(sourceCode);
u256 value(18);
u256 id(0x1234);
for (bool manually: {true, false})
{
callContractFunctionWithValue("deposit(bytes32,bool)", value, id, manually);
BOOST_REQUIRE_EQUAL(numLogs(), 1);
BOOST_CHECK_EQUAL(logAddress(0), m_contractAddress);
BOOST_CHECK_EQUAL(h256(logData(0)), h256(u256(value)));
BOOST_REQUIRE_EQUAL(numLogTopics(0), 3);
BOOST_CHECK_EQUAL(logTopic(0, 0), util::keccak256(string("Deposit(address,bytes32,uint256)")));
BOOST_CHECK_EQUAL(logTopic(0, 1), h256(m_sender, h256::AlignRight));
BOOST_CHECK_EQUAL(logTopic(0, 2), h256(id));
}
)
} The resulting semantic test may look like this: contract ClientReceipt {
event Deposit(address indexed _from, bytes32 indexed _id, uint _value);
function deposit(bytes32 _id) public payable {
emit Deposit(msg.sender, _id, msg.value);
}
}
// ====
// compileViaYul: also
// ----
// deposit(bytes32), 18 wei: 0x1234 ->
// logs.numLogs() -> 1
// logs.logAddress(uint256): 0 -> contract.address()
// logs.logData(uint256): 0 -> 18
// logs.numLogTopics(uint256): 0 -> 3
// logs.logTopic(uint256,uint256): 0, 0 -> utils.keccak256("Deposit(address,bytes32,uint256)")
// logs.logTopic(uint256,uint256): 0, 1 -> logs.sender()
// logs.logTopic(uint256,uint256): 0, 2 -> 0x1234 It would also be nice to support builtins (e.g. Without builtins that can be used as expectations this test would look like this: // deposit(bytes32), 18 wei: 0x1234 ->
// logs.numLogs() -> 1
// logs.logAddress(uint256): 0 -> 0x0fdd67305928fcac8d213d1e47bfa6165cd0b87b
// logs.logData(uint256): 0 -> 18
// logs.numLogTopics(uint256): 0 -> 3
// logs.logTopic(uint256,uint256): 0, 0 -> 0x19dacbf83c5de6658e14cbf7bcae5c15eca2eedecf1c66fbca928e4d351bea0f
// logs.logTopic(uint256,uint256): 0, 1 -> 0x1212121212121212121212121212120000000012
// logs.logTopic(uint256,uint256): 0, 2 -> 0x1234 |
486b2c1
to
7ecf16c
Compare
Please make the test expectation more readable: After each function that causes an event or a log, add one line per log, that looks as follows:
With the following simplifications:
All emitted events always have to be mentioned. I think we should not make this a query to a builtin that can be there or cannot be there. Instead, the events belong to the function call. |
6418b58
to
2c3218b
Compare
I implemented this with the help of a builtin, but maybe it can be simplified more. I introduced a new builtin If we have the following contract: contract ClientReceipt {
event D(address indexed _from, bytes32 indexed _id, uint _value);
function deposit(bytes32 _id) public payable {
emit D(msg.sender, _id, msg.value);
}
} The test looks right now like this:
As this is not an anonymous event, the builtin will check whether the provided event signature hash is equal to If we would change the event // logs.expectEvent(uint256,string): 0, "D3(address,bytes32,uint256)" -> 0x1234, 0x1212121212121212121212121212120000000012, 18 I implemented a simple tracking of event producers and it's consumer. The idea here is that only non-builtins can produce events, and only builtins - especially the This information is used to generate a failure during test execution, if not all produced events where consumed. Right now I only implemented the detection and visualisation that some events where not consumed. You can see this in the failing tests right now, e.g. here. The message will also show the event signature that was matching with the hash defined in
This is exactly what my plan is for tomorrow. Right now I only print the topics, and a big blob of data without interpreting the types. The only missing thing is right now that I don't support your |
Don't forget that we ideally not just want to have an error, if the logs in the expectations do not match the actually emitted events, but we'd also like to be able to have "(u)pdate" in an |
struct log_record | ||
{ | ||
size_t index; | ||
/// The address of the account which created the log. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most of these comments are redundant. What I would like to know the meaning of is index
. Is it its position in the transaction or the call?
@@ -51,6 +51,25 @@ class ExecutionFramework | |||
{ | |||
|
|||
public: | |||
struct log_record |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
struct log_record | |
struct LogRecord |
@@ -51,6 +51,25 @@ class ExecutionFramework | |||
{ | |||
|
|||
public: | |||
struct log_record |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the same as in MockHost except for the index - do we need the duplication?
for (auto logIdx: m_touchedLogs[consumer]) | ||
// All logs that where touched by the consumer, will be marked as | ||
// touched within the producer. | ||
touchLog(producer->call(), logIdx); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would say let's rather check that the number is the same and they are equal individually.
2c3218b
to
a8c179d
Compare
8bf54d2
to
febe7a3
Compare
30df8ae
to
055c73c
Compare
37bf423
to
a9a6de2
Compare
// deposit(bytes32), 28 wei: 0x1234 -> 0x1c | ||
// logs.expectEvent(uint256,string): 0, "A(address,bytes32,uint256)" -> 0x1212121212121212121212121212120000000012, 0x1234, 0x1c | ||
// logs.expectEvent(uint256,string): 1, "" -> 0x1c, 0x1212121212121212121212121212120000000012, 0x1234 | ||
// logs.expectEvent(uint256,string): 2, "C(address,bytes32,uint256)" -> 0x1234, 0x1212121212121212121212121212120000000012, 0x1c |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// deposit(bytes32), 28 wei: 0x1234 -> 0x1c | |
// logs.expectEvent(uint256,string): 0, "A(address,bytes32,uint256)" -> 0x1212121212121212121212121212120000000012, 0x1234, 0x1c | |
// logs.expectEvent(uint256,string): 1, "" -> 0x1c, 0x1212121212121212121212121212120000000012, 0x1234 | |
// logs.expectEvent(uint256,string): 2, "C(address,bytes32,uint256)" -> 0x1234, 0x1212121212121212121212121212120000000012, 0x1c | |
// deposit(bytes32), 28 wei: 0x1234 -> 0x1c | |
// - emit A(address,bytes32,uint256) -> 0x1212121212121212121212121212120000000012, 0x1234, 0x1c | |
// - emit B(...) -> 0x1c, 0x1212121212121212121212121212120000000012, 0x1234 |
7143c5b
to
767514e
Compare
59ddf67
to
9489582
Compare
Replaced by #11050. |
Depends on #10867.
Fixes #6902.
support for builtin functions.
implementation of all event specific builtin functions.
add implicit check for events.
support automatic expectation generation (easily readable), if events where found but not checked for.
add useful builtins that can be used within expectations. (e.g.
contract.address
,utils.keccak256
) - maybe this should be done in another PRadd log specific expectation builtins e.g.
logs.sender
.move event specific tests from
SolidityEndToEndTests
to semantic tests.