A Basic Smart Contract

pragma solidity ^0.4.24;

contract Campaign {

    address public owner;
    uint public deadline;
    uint public goal;
    uint public fundsRaised;
    bool public refundsSent;

    event LogContribution(address sender, uint amount);
    event LogRefundsSent(address funder, uint amount);
    event LogWithdrawal(address beneficiary, uint amount);

    struct FunderStruct {
        address funder;
        uint amount;
    }

    FunderStruct[] public funderStructs;

    constructor ( uint _duration, uint _goal) public {
        owner = msg.sender;
        deadline = block.number + _duration;
        goal = _goal;
    }

    function isSuccess() public constant returns(bool isIndeed) {
        return (fundsRaised >= goal);
    }

    function hasFailed() public constant returns(bool hasIndeed) {
        return (fundsRaised < goal && block.number > deadline );
    }

    function contribute() public payable returns(bool success) {
        require (msg.value != 0);
        require (!hasFailed());
        require (!isSuccess());
        fundsRaised += msg.value;
        FunderStruct memory newFunder;
        newFunder.funder = msg.sender;
        newFunder.amount = msg.value;
        funderStructs.push(newFunder);
        emit LogContribution(msg.sender, msg.value);
        return true;
    }

    function withdrawFunds() public returns(bool success) {
                require (msg.sender == owner);
                require (isSuccess());
                uint amount  = address(this).balance;
                owner.transfer(amount);
                emit LogWithdrawal(owner, amount);
                return true;
    }

    function sendRefunds() public returns(bool success) {
        require (msg.sender == owner);
        require (!hasFailed());
        require (!refundsSent);
        uint funderCount = funderStructs.length;
        for(uint i = 0; i < funderCount; i++){
            funderStructs[i].funder.transfer(funderStructs[i].amount);
            emit LogRefundsSent(funderStructs[i].funder, funderStructs[i].amount);
        }
        refundsSent = true;
        return true;
    } 
}

 Compiler Version

pragma solidity ^0.4.24;

Start a contract by defining the compiler version. Your contracts may be deployed by other people and the pragma indicates the compiler version you wrote and tested your contract with.

 Define Your Nouns

Solidity is a strongly typed language which means you have to define the type of each kind of variable you intend to use at the top of your contract.

address public owner;
uint public deadline;
uint public goal;
uint public fundsRaised;
bool public refundsSent;

Generally you have value types like string, booleans at the top and reference types like arrays and structs below them.

struct FunderStruct {
        address funder;
        uint amount;
    }

FunderStruct[] public funderStructs;

The last type of noun is an event for each time the state changes. You don’t need to do this, but it affords you the ability to track events as the state changes in your contract.

event LogContribution(address sender, uint amount);
event LogRefundsSent(address funder, uint amount);
event LogWithdrawal(address beneficiary, uint amount);

 Define The Contract’s Functions

The next thing you want to think of is what your contact does. If your variables are your contract’s nouns then you functions are it’s verbs. I just write out a descriptive name for each intended function and leave it at that.

function contribute() {}
function withdrawFunds() {}
function sendRefunds()  {}

 Declare Each Function’s Characteristics

The next thing to do is go through each of the functions and declare whether they are public, private, internal, external, view only, or pure functions. Functions that deal with ether need to be marked payable. I’ll also define what the function returns. If it doesn’t necessarily return anything then I’ll return a boolean so that I know everything went according to plan.

function contribute() public payable returns(bool success) {...}

 Write Your Constructor

A constructor is a function that runs once when the contract is deployed. Use it to set the initial state fo your contract.

constructor ( uint _duration, uint _goal) public {
        owner = msg.sender;
        deadline = block.number + _duration;
        goal = _goal;
    }

 Flesh Out Your Functions

A function will usually start with a series of checks, then change some state and then return a value.

A check is just an if statement that throws. The throw keyword is deprecates so you use Revert(), Assert(), and Require() instead.

Require() is what you should be using most validations. It will stop the function in its tracks.

Assert() is aggressive. It lets you check for conditions that should never occur. It will stop the function and steal all your gas.

Revert() will stop your function and undo all state changes that happened in the function up till that point.

If you have more than a simple variable to check then write the logic out in a separate function and pass the function in for readability

function hasFailed() public constant returns(bool hasIndeed) {
        return (fundsRaised < goal && block.number > deadline );
    }

function isSuccess() public constant returns(bool isIndeed) {
        return (fundsRaised >= goal);
    }

You then pass require (!hasFailed()); and require (!isSuccess()); in with the other checks.

 function contribute() public payable returns(bool success) {

        require (msg.value != 0);
        require (!hasFailed());
        require (!isSuccess());

        fundsRaised += msg.value;
        FunderStruct memory newFunder;
        newFunder.funder = msg.sender;
        newFunder.amount = msg.value;
        funderStructs.push(newFunder);

        LogContribution(msg.sender, msg.value)
        return true;
    }

Emitting an event at the end of a function, before you return a value, makes it easier to test and its also helpful because the front end client can access these events.

I hope this was a helpful way to think about each of the steps that go into writing a basic smart contract.

References

 
0
Kudos
 
0
Kudos

Now read this

An Introduction to Software Testing

Qunit is a javascript framework that you can use to test software as you build it. Learning how to use qunit so that you can test software as you build it will save you time in the long run. Testing will ensure that you only write the... Continue →