Search code examples
unit-testingeventsethereumsoliditysmartcontracts

Testing ethereum events directly in solidity with truffle


I found the following question for testing event logging in truffle using javascript:

Test ethereum Event Logs with truffle

But truffle also supports writing tests directly in solidity. However, I can't find any documentation for how to test event logging in solidity. Can somebody help me with this?


Solution

  • General remarks:

    Be aware that a smart contract can't access events. Events are by design just accessible from outside a smart contract. They are not directly stored in the blockchain. This means that you will not be able to test with pure solidity.

    The Log and its event data is not accessible from within contracts (not even from the contract that created them). Source: https://solidity.readthedocs.io/en/v0.5.3/contracts.html#events

    Testing events with truffle works, just follow these steps:

    1) Create simple contract that emits events (contracts/EventEmitter.sol):

        pragma solidity 0.5.12;
    
        contract EventEmitter {
    
        // ---- EVENTS -----------------------------------------------------------------------------------------------------
        event ConstructorDone(address owner, string message);
        event Counter(uint64 count);
    
        // ---- FIELDS -----------------------------------------------------------------------------------------------------
        uint64 private _count = 0;
        string constant _message = '0x0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789';
    
        // ---- CONSTRUCTOR ------------------------------------------------------------------------------------------------
        constructor() public {
            emit ConstructorDone(msg.sender, _message);
        }
    
        // ---- STATISTICS FUNCTIONS ---------------------------------------------------------------------------------------
        function getCount() public view returns (uint count) {
            return _count;
        }
    
        // ---- CORE FUNCTIONS ---------------------------------------------------------------------------------------------
        function increment() public {
            _count++;
            emit Counter(_count);
        }
    }
    

    2) Create test contract (test/TestAnEventEmitter.sol):

    pragma solidity 0.5.12;
    
    import "truffle/Assert.sol";
    import "../contracts/EventEmitter.sol";
    
    contract TestAnEventEmitter {
    
        EventEmitter private eventEmitter;
    
        uint eContracts = 0;
    
        address private owner;
    
        function assertCount() private {
            Assert.equal(eventEmitter.getCount(), eContracts, "Unexpected count of created contracts");
        }
    
        constructor() public{
            eventEmitter = new EventEmitter();
            owner = address(this);
        }
    
    }
    

    3) Create Test Code (test/TestAnEventEmitter.js):

    const EventEmitter = artifacts.require("EventEmitter");
    const truffleAssert = require('truffle-assertions');
    
    /** Expected number of counter */
    var eCount = 0;
    
    /** The Contract's instance */
    var eventEmitter;
    
    global.CONTRACT_ADDRESS = '';
    
    async function assertContractCount() {
        assert.equal(await eventEmitter.getCount.call(), eCount, "Wrong number of created contracts");
    }
    
    contract('EventEmitter', async () => {
    
        before(async () => {
            eventEmitter = await EventEmitter.new();
        });
    
        describe("1.1 Basic", function () {
    
            it("1.1.1 has been created", async () => {
                global.CONTRACT_ADDRESS = eventEmitter.address;
                console.log('        contract => ' + global.CONTRACT_ADDRESS);
                await assertContractCount();
            });
    
            it("1.1.2 should emit ConstructorDone event", async () => {
                // Get the hash of the deployment transaction
                let txHash = eventEmitter.transactionHash;
    
                // Get the transaction result using truffleAssert
                let result = await truffleAssert.createTransactionResult(eventEmitter, txHash);
    
                // Check event
                truffleAssert.eventEmitted(result, 'ConstructorDone', (ev) => {
                    console.log('        owner => ' + ev.owner);
                    return true;
                });
            });
        });
    
        describe("1.2 Check calls of increment()", function () {
    
            it("1.2.1 first call should increase the counts correctly", async () => {
                // Pre-Conditions
                await assertContractCount();
    
                // Creation
                let tx = await eventEmitter.increment();
                eCount++;
    
                // Expected Event
                truffleAssert.eventEmitted(tx, 'Counter', (ev) => {
                    return parseInt(ev.count) === eCount;
                });
    
                // Post-Conditions
                await assertContractCount();
            });
    
            it("1.2.2 second call should increase the counts correctly", async () => {
                // Pre-Conditions
                await assertContractCount();
    
                // Creation
                let tx = await eventEmitter.increment();
                eCount++;
    
                // Expected Event
                truffleAssert.eventEmitted(tx, 'Counter', (ev) => {
                    return parseInt(ev.count) === eCount;
                });
    
                // Post-Conditions
                await assertContractCount();
            });
    
            it("1.2.3 third call should increase the counts correctly", async () => {
                // Pre-Conditions
                await assertContractCount();
    
                // Creation
                let tx = await eventEmitter.increment();
                eCount++;
    
                // Expected Event
                truffleAssert.eventEmitted(tx, 'Counter', (ev) => {
                    return parseInt(ev.count) === eCount;
                });
    
                // Post-Conditions
                await assertContractCount();
            });
        });
    });
    
    

    4) Run Tests:

    $ truffle test
    Using network 'development'.
    
    
    Compiling your contracts...
    ===========================
    > Compiling ./test/TestAnEventEmitter.sol
    
    
    
      Contract: EventEmitter
        1.1 Basic
            contract => 0xeD62E72c2d04Aa385ec764c743219a93ae49e796
          ✓ 1.1.1 has been created (56ms)
            owner => 0xbD004d9048C9b9e5C4B5109c68dd569A65c47CF9
          ✓ 1.1.2 should emit ConstructorDone event (63ms)
        1.2 Check calls of increment()
          ✓ 1.2.1 first call should increase the counts correctly (142ms)
          ✓ 1.2.2 second call should increase the counts correctly (160ms)
          ✓ 1.2.3 third call should increase the counts correctly (156ms)
    
    

    Full sources (with package.json, etc.): https://github.com/MarkusSprunck/ethereum-event-scan

    More about events and monitoring: https://www.sw-engineering-candies.com/blog-1/Ethereum-Event-Explorer-for-Smart-Contracts

    (Disclaimer: I'm the author of this project and blog)