Venom In Action. Voting system contracts.

This section will show you how to create your own SMV simple system. The real purpose of this guide - to explore some common mechanics like address calculation, external callings and bounce handling.

No further ado. Let's start with familiar command

npx locklift init --path my-smv

As you previously read, we need to implement two smart contracts. There is no external dependencies for this guide. Start with Vote contract. We have a pretty clean state and constructor without something unusual

Vote.sol
pragma ever-solidity >= 0.61.2;
pragma AbiHeader expire;
pragma AbiHeader pubkey;

import "./Ballot.sol";

contract Vote {
    uint16  static _nonce;
    TvmCell static _ballotCode;

    uint256 _managerPublicKey;
    uint32  _acceptedCount;
    uint32  _rejectedCount;

    constructor(
        uint256 managerPublicKey,
        address sendRemainingGasTo
    ) public {
        tvm.accept();
        tvm.rawReserve(0.1 ever, 0);
        _managerPublicKey = managerPublicKey;
        sendRemainingGasTo.transfer({ value: 0, flag: 128, bounce: false });
    }
}

Next function we need - deployBallot. It realize popular "deploy contract from contract" mechanic well-descripted here. We should just use tvm.buildStateInit function, fill varInit section by future values of our Ballot contract static variables and use keyword new for deploying.

Well, the votes will be stored in our Vote contract. That's why we need a special function, that can be called only by Ballot contract. Ballot contract will call this function and pass a vote (accept or reject) into. But how we can define a function, that can be called only by contracts with concrete code (by contracts, that was deployed by Vote contract)?

It can't be any easier. Address of any contract can be definitely calculated, if you know state init variables, public key and contract code:

That is the way out! TokenWallets of TIP-3 implementation working the same way to transfer tokens (one wallet calls another wallet's acceptTransfer function).

The last thing we need is a getDetails view function to return results of our vote

Bring it all together

Now let's deal with Ballot contract. There is no something special in state and constructor:

Let's talk about activation mechanic. In constructor we already reserved little more venoms. We made it with purpose, that fee for external call will be payed from contract balance. That way of gas management allows us to transfer external calls fee paying to user responsibility. But activate method shouldn't be called by somebody unauthorized, so we just use require keyword with comparing msg.pubkey and _managerPublicKey stored in state init. Of course you need to call tvm.accept() function. Simply put, this call allows contract to use it's own balance for execution pay.

Let's implement main function of our Ballot - vote.

Pay attention to imports. We have import "./interfaces/IVote.sol"; . It's just an interface for calling our Vote contract (just like for EVM if you know what I mean).

Let us now return for vote function

That's all. Vote contract will check our Ballot address by calculating it, as you remember, and vote will be accept. But what if Vote calls will fail because of some reason (low gas attached or yet network problem!)? Our Ballot will be marked as used (_used state variable will be set as true, and we can't call vote once again). For solve this problems, TVM has a bounce messages and onBounce function for handling it. Let's deal with it by example

That's it. Now let's bring it all together.

Do not forget about tests and scripts. We won't show any scripts in this guideline just because there is no something special in. All source ode with deploy script and simple test suite are available in repo. Next section will show you some enhancing for this code.

Last updated