Skip to content

Instantly share code, notes, and snippets.

@barkthins
Last active March 2, 2016 06:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save barkthins/c2e527af82056542cd7d to your computer and use it in GitHub Desktop.
Save barkthins/c2e527af82056542cd7d to your computer and use it in GitHub Desktop.
#!/usr/bin/env node
var Promise = require('bluebird');
var fs = require('fs');
var test = require('blue-tape');
require('../web3E.js');
var Web3 = require('web3');
var web3 = new Web3();
var solc = require('solc');
// we shouldn't have to do this after web3E has done it, but can't
// get that working properly...
Promise.promisifyAll(web3.eth);
test("What happens when a contract sends ether it does not have", function(tid) {
web3.setupDefault();
var myContract;
var source = fs.readFileSync("tape/send.sol", 'utf8');
var compiled = solc.compile(source,1);
var code = compiled.contracts.sendit.bytecode;
var abi = JSON.parse(compiled.contracts.sendit.interface);
web3.eth.deployContract(code, abi)
.then(function(contract) {
myContract = contract;
Promise.promisifyAll(myContract);
Promise.promisifyAll(myContract.incrData);
// the contract sends ether it doesn't have. This results in
// the send failing but other side effects still happen.
// i.e. no throw in solidity. Note the contract takes the 5000 Wei that was sent,
// meaning this function would send ether every other time.
sent = 5000;
// this is not a good place to specify gas. Should be on the ABI function, not sprinkled into code.
return myContract.incrData.sendTransactionAsync({value:sent, gas:600000})
}).then(function(result) {
// timeout should never be in application code, it's deployment and ABI function dependent.
// XXX if we run out of gas the tests below may be invalid and give false positive or false negative
return web3.eth.awaitConsensus(result).timeout(30000,"ethereum didn't mine it")
}).then(function() {
// XXX what happens if this times out because of node problem? Should throw from library
return myContract.dataAsync()
}).then(function(res) {
tid.equal(res.toString(10), '1', "Side effect of incrementing occured");
return myContract.resultAsync()
}).then(function(res) {
tid.equal(res, false, "Got a failure to send the ether");
return myContract.sendersBalanceAsync()
}).then(function(res) {
return web3.eth.getBalanceAsync(myContract.address)
}).then(function(bal) {
tid.equal(bal.toNumber(), sent, "Ether balance is what we sent in the incrData call");
tid.end();
});
});
@barkthins
Copy link
Author

Note that awaitConsensus is a function I wrote the performs the whole txhash->txreceipt event wait routine, instead of having that all over my application code. You can see the above code is straight to the point of what I'm trying to test with the solidity contract.

Requirements that need to be added to this code (but preferably not IN this code, but separately):

  1. be able to set timeouts that can vary depending on whether we're in production or a test environment, without polluting the code with that information. In addition some critical ABIs should wait e.g. 100 blocks for absolute confidence and some ABI calls can just wait for the local node to mine the transaction. Normally with Promises we'd put a timeout after every Promise but that pollutes the code with knowledge about what environment we're in and what confidence we need on a particular API.
  2. check to see if gas ran out, because the code won't be correct if it didn't. NOTE: In the above code, awaitConsensus performs this function and throws if gas ran out.. Hopefully you can see in this code that with the traditional txhash->txreceipt->checkreceipt check and then see if gasSent == gasReceived. You'll find that it uglifies the code tremendously with code that has nothing to do with the logic of the solidity code we're testing.
  3. Check to see how much gas was used. However that's an analytic function, it shouldn't appear in the application code. Usually we'd just log such a thing or warn that we're within 90%+ of gasSent. Note the gasSent and the ABI being called have to be logged, but currently awaitConsensus doesn't have that knowledge.
  4. Be able to tid.equal() on a log event. Hopefully you can see the obvious place to do this would be to have awaitConsensus() return the transaction logs as an object. The current system of having a separate callback makes that much more difficult to do.

@barkthins
Copy link
Author

here's the solidity contract I'm testing:

contract sendit {
    uint256 public data;
    bool public result;
    uint256 public sendersBalance;

    function test() {
    }

    // if you send 5000 Wei to this function it will
    // successfully send ether every other transaction
    function incrData() {
        data += 1;
        // this should fail if this contract doesn't have enough ether
        // however there's no throw.  data is still incremented,
        // and ether is still added to this contract instance's balance
        // if it was sent in this transaction
        result = msg.sender.send(10000);
        sendersBalance = msg.sender.balance;
    }
}

@barkthins
Copy link
Author

BTW this is the type of test contract and test code I write every time I encounter something non-obvious, to help the next noob and remind me when I forget.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment