npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@jbrower95/bsan

v1.0.3

Published

Block Sanitizer: Assert on-chain state changes in your truffle tests.

Downloads

5

Readme

bsan: Better testing for truffle

Philosophy

Smart contracts are above all a mechanism for updating state. While line and branch coverage are essential, your tests are ultimately responsible for making sure that on-chain state is updated correctly. Remembering to monitor everything you care about is hard. BSAN makes this easier.

BSAN

BSAN (Blockchain Sanitizer) is a simple addition to Truffle's testing suite that supports tracking and verifying how your code changes state on-chain.

BSAN allows you to track:

  • ETH account balances.
  • ERC20 account balances.
  • Smart contract variables.

BSAN provides a framework to extend to arbitrary tracking of chain-state, using a simple but powerful methodology that integrates directly with Mocha.

Should I use bsan?

If you:

  • Write Smart Contract tests using truffle and ganache, and
  • Are writing tons of imperative logic to check fields that your contracts may changed,
  • Want to understand how your code affects on-chain state better, so that you can make changes more confidently, then

You 100% should be monitoring all state-changes produced by your program with BSAN.

Getting Started

In your project: npm i --save-dev @jbrower95/bsan

// some erc20 contract
contract TokenContract is ERC20 {  ... }

// a sample solidity contract where you can set the owner by bidding the most ether.
contract MyContract {
  address owner;
  uint256 amountPaid;

  function setOwner() public payable {
    if (msg.value > amountPaid) {
      if (amountPaid > 0) {
        // refund
        bool sent = address(this.owner).send(amountPaid);
        require(sent, "Failed to refund ether");
      }

      this.owner = msg.sender;
      this.amountPaid = msg.value;
    }
  }
}

In your truffle test:

const {ETHWalletMonitor, ContractMonitor} = require('bsan');
const {eth2wei} = require('bsan/utils.js');

// Your truffle `contract` test.

let myContract = null; // load this later.

contract.stateful(
  "MySmartContract",
  // after this state is created, it's available via the global "S"
  (accounts) => new DappState(
    wallets={
      account0: new ETHWalletMonitor(accounts[0]),
    },
    erc20={},
    contract={
      myContractOwner: new ContractMonitor(myContract, myContract.owner),
    },
  ),
  (accounts) => {
    before(async () => {
      // load any contracts you need.
      myContract = await MyContract.deployed({from: accounts[4]});
    });

    it("Bidding 0 causes the owner to not be set", async function () {
      await S.wallets.account0.call(S, myContract.setOwner, {value: eth2wei(0).toString()});
      await S.wallets.account0.expectOnlyConsumedGas("If bidding nothing, you should not receive ownership.");
    });

    it("Bidding 1 causes a new owner to be set", async function () {
      await S.wallets.account1.call(S, myContract.setOwner, {value: eth2wei(1).toString()});

      // assert wallet balance falls.
      await S.wallets.account1.expectFallsBy(eth2wei(1), "When bidding 1 eth, 1 eth is paid.");

      // assert new owner is set.
      await S.contract.myContractOwner.expect(account[1]);

      // bid even more from another account.
      await S.wallets.account0.call(S, myContract.setOwner, {value: eth2wei(2).toString()});
      await S.wallets.account1.expectRisesBy(eth2wei(1), "Account 1 should receive a refund.");
      await S.wallets.account0.expectFallsBy(eth2wei(2), "Account 0 pays 2 ETH to be the owner.");
      // assert new owner is set.
      await S.contract.myContractOwner.expect(account[0]);
    });
});

You should start to see how these tests work. In the beginning, you list the things you care about on-chain.

  new DappState(
    wallets={
      // monitor ETH sends/receives from this account
      account0: new ETHWalletMonitor(accounts[0]),
    },
    erc20={
      // monitor ERC20 balance changes on this account for the
      // truffle contract instance `token`
      account0: new ERC20Monitor(token, accounts[0])
    },
    contract={
      // monitor value changes to the `myContract.owneccr` field.
      myContractOwner: new ContractMonitor(myContract, myContract.owner),
    },
  )

Every time you call a function (via. await S.wallets.account1.call(...)) your state will be asserted to make sure that all the things you care about haven't changed beyond your expectations.

At the end of your test, every monitor will be checked to make sure that its final value matches what is currently on-chain. If you haven't asserted something, you'll get an error describing how the value on-chain differed from your expectations.

With the code example above, if you uncomment any of the expect() calls, the test will fail. Similarly, if you change any of the logic in the contract, your tests should also fail if the end-state on chain would deviate.

Calling Contract Functions

With an ETHWalletMonitor, call your solidity contract functions by using

await state.wallets.<name>.call(state, contractInstance.function, param1, param2...)

If you need to pass value into a txn, you can pass a normal web3 dictionary as the last argument to .call(). e.g ```{value: "100000000"}

When using this syntax, bsan will automatically keep track of gas consumption for you, which would otherwise affect your ganache ETH account balances by the end of your test.

expect() / state changes

By accessing your DappState (which describeDapp() provides to your tests -- see "S" above) you get direct access to your monitors.

S.wallets.* - all of the ETH wallets you monitor. S.contract.* - all of the contract variables you monitor. S.erc20.* - all of the erc20 account balances you monitor.

With monitors, you can do things like:

  const monitor = S.wallets.account0;

  monitor.expectFallsBy(eth2wei(1), "Account falls by 1 eth.");
  monitor.expectRisesBy(eth2wei(1), "Account gains 1 eth.");
  monitor.expect(eth2wei(100), "Account has exactly 1 eth.");

Contract values

With ContractMonitor, you can observe a field on a contract.

  • If the field requires parameters, you can pass in an array of static parameters to provide when querying the field.
  • If the field is a struct, you can provide a keypath to observe a specific scalar field on that struct. (as of writing, you cannot not yet observe the entire struct, but fields individually can still be useful.) Keypaths should follow the format of "key.path.part", and do not yet support indexing into lists.
  // assume we have mapping <address, uint> for some balance tracking.

  //

    new DappState(
      contract={
        // monitor value changes to the `myContract.owneccr` field.
        mapping: new ContractMonitor(someContractInstance, someContractInstance.myMapping, [accounts[0]]),
        someKeypathField: new ContractMonitor(someContractInstance, someContractInstance.myStructMapping, [accounts[0]], keypath="my.field"),
      }
    );

  const monitor = S.contract.mapping;
  monitor.expect(address[0]);

  // then, maybe execute some logic that changes the mapping on-chain.
  // ...

  monitor.expect(address[1]);

Contributing

New on-chain data:

If you'd like to track some other piece of on-chain state, head to ./monitors and check out monitor.js for more information about the monitor abstraction.

Testing + Bugs:

If you find an issue in BSAN and want to support the project with a patch, submit a pull request and I'll review quickly. Thanks!

License

Open source / MIT. See LICENSE for more information.

Tip Jar

ETH: jaymothy.eth

  • If you like BSAN and find it useful, please reach out! Would love to here what folks have to say about the framework.
  • If BSAN helped you catch a bug or made your development easier, feel free to send a tip to jaymothy.eth, or help contribute for others :)