1

I want to raise events from inside a piece of assembly code.

I've extended the answer here with the function AssemblyEvent() which currently causes a runtime error.


Working example:

pragma solidity ^0.4.21;

contract Demo {
    event Deposit(
        address indexed _from,
        bytes32 indexed _id,
        uint256 _value
    );

    function highLevelEvent() public {
        bytes32 _id = hex"420042";
        emit Deposit(msg.sender, _id, 123);
    }

    function lowLevelEvent() public {
        bytes32 _id = hex"420042";
        log3(
            bytes32(uint256(123)),
            bytes32(keccak256("Deposit(address,bytes32,uint256)")),
            bytes32(uint256(uint160(msg.sender))),
            _id
        );
    }
    // ##########################
    // this function doesn't work
    // ##########################

    function AssemblyEvent() public {
        bytes32 _id = hex"420042";
        bytes32 t1 = bytes32(uint256(123));
        bytes32 t2 = bytes32(keccak256("Deposit(address,bytes32,uint256)"));
        bytes32 t3 = bytes32(uint256(uint160(msg.sender)));

        assembly {
            let s := 0x20
            let p := _id
            log3(p, s, t1, t2, t3)
        }
    }
}

What have I done wrong in the above code? Also, as Assembly only has functions up to log4, does this mean that 4 the maximum number of variables I can create logs for?

2

What have I done wrong in the above code?

With inline assembly the log3 function organizes the stack according to the given parameters and then executes the LOG3 opcode. The stack inputs for an event, top to bottom, are as follows:

  • memory location where the unindexed event data begins
  • length of the unindexed event data within the memory
  • indexed topic(s)

Ethervm provides a good visualization of this.

In your example - by declaring let p := _id and then giving p as the top value in the stack when calling to log3, you are telling the EVM to obtain the unindexed data starting at location 420042. This is well outside of the current memory size, and so the transaction fails from an invalid operation.

To achieve the desired results with minimal modification to the given example:

function AssemblyEvent() public {
    bytes32 _id = hex"420042";
    bytes32 t1 = bytes32(uint256(123));
    bytes32 t2 = bytes32(keccak256("Deposit(address,bytes32,uint256)"));
    bytes32 t3 = bytes32(uint256(uint160(msg.sender)));
    

    assembly {
        let p := add(msize, 0x20)
        mstore(p, t1)
        log3(p, 0x20, t2, t3, _id)
    }
}

In my example code I first assign p as a memory pointer that is 32 bytes beyond the size of the existing memory, so that I will not overwrite any existing data. I then store t1 at that location. Finally I call log3 with the memory pointer, a length of 32 bytes, and the sequence of indexed topics.

Also, as Assembly only has functions up to log4, does this mean that 4 the maximum number of variables I can create logs for?

Four is the maximum number of indexed topics that you can return in an event. The first topic is the hash of the inputs, used to identify the event. You may additionally index up to 3 values within the event.

From the Solidity documentation:

You can add the attribute indexed to up to three parameters which adds them to a special data structure known as “topics” instead of the data part of the log. If you use arrays (including string and bytes) as indexed arguments, its Keccak-256 hash is stored as a topic instead, this is because a topic can only hold a single word (32 bytes).

All parameters without the indexed attribute are ABI-encoded into the data part of the log.

Topics allow you to search for events, for example when filtering a sequence of blocks for certain events. You can also filter events by the address of the contract that emitted the event.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.