@wearekickback/contracts-kovan

Kickback smart contracts

Usage no npm install needed!

<script type="module">
  import wearekickbackContractsKovan from 'https://cdn.skypack.dev/@wearekickback/contracts-kovan';
</script>

README

Contracts

Build Status Coverage Status

This repo contains all the Kickback contracts. The master branch is the main branch, and contains the productions versions of the contracts.

Using the contracts

To use these contracts in a Dapp first install our NPM org:

npm i @wearekickback/contracts

Then, using truffle-contract you can import and use the Deployer contract definition and use it as such:

const promisify = require('es6-promisify')
const TruffleContract = require('truffle-contract')
const Web3 = require('web3')
const { Deployer } = require('@wearekickback/contracts')

async init = () => {
  const web3 = new Web3(/* Ropsten or Mainnet HTTP RPC endpoint */)

  const contract = TruffleContract(Deployer)
  contract.setProvider(web3.currentProvider)

  const deployer = await contract.deployed()

  // deploy a new party (see Deployer.sol for parameter documentation)
  await deployer.deploy('My event', 0, 0, 0, oneweek, owneraddress, tokenaddress)

  const events = await promisify(deployer.contract.getPastEvents, deployer.contract)('NewParty')

  const { returnValues: { deployedAddress } } = events.pop()

  console.log(`New party contract deployed at: ${deployedAddress}`)
}

Dev guide

Pre-requisites:

Setup Truffle config

Copy .deployment-sample.js to .deployment.js and edit the values accordingly.

Install dependencies and do basic setup

yarn
yarn setup

Setup parameters for Truffle config:

cp .deployment-sample.js .deployment.js

Run local chain

npx ganache-cli --accounts 500

Run tests

yarn test

Deploy contracts to local network

Run:

yarn deploy:local

This will also call a script to update the app and server repo clones if you've checked them out as sibling folders.

Simulation

** NOTE: This is currently not up to date so more likely not working **

To deploy a new party onto the local test network:

yarn seed:party -i test

This command has a number of options which allow you additionally simulate the full lifecycle of a party:

$ yarn seed:party --help

Usage: deployNewParty [options]

Options:

  -i, --id <id>            Id of party (obtain from UI /create page)
  --ropsten                Use Ropsten instead of local development network
  --rinkeby                Use Rinkeby instead of local development network
  --kovan                  Use Rinkeby instead of local development network
  --mainnet                Use Mainnet instead of local development network
  --admins <n>             Number of additional party admins to have
  -c, --cancelled          Whether to mark the party as cancelled
  -t, --coolingPeriod [n]  How long the cooling period is in seconds (default: 604800)
  -d, --deposit [n]        Amount of ETH attendees must deposit (default: 0.02)
  -f, --finalize <n>       Finalize the party with the given no. of attendees
  -p, --participants <n>   Maximum number of participants
  -r, --register <n>       Number of participants to register
  -w, --withdraw <n>       Number of attendees to withdraw payouts for
  -h, --help               output usage information

So, for example, to create party with max. 100 participants, upto 50 actually registered, with 25 having actually attended, and 12 having withdrawn their payouts after the party has ended. With an added cooling period of 1 millisecond to allow your to test the clear functionality immediately.

yarn seed:party -i test -p 100  -r 50 -a 25 -w 12 -e -t 1

The script actually uses truffle-config.js to work out how to connect to the development network. If you want to seed a party on e.g. Ropsten then you can do by supplying the --ropsten flag:

yarn seed:party --ropsten -i test -p 100  -r 50 -a 25 -w 12 -e -t 1

Note: For public network seeding to work you will need to have configured valid values in .deployment.js (see "Deployment to public networks" below).

 Tests

yarn coverage

Deployment to public networks

Edit .deployment.js and fill in the company mnemonic, Infura key (obtain from 1Password) and Etherscan key (obtain from 1Password).

Releases are done automatically via CI. Prior to doing a release, ensure the latest compiled contracts have been deployed to both test nets and the mainnet:

$ yarn deploy:ropsten
$ yarn deploy:rinkeby
$ yarn deploy:kovan
$ yarn deploy:mainnet

Once contracts are deployed to public network, run the verify script to verify the source code on Etherscan.

truffle run verify Deployer --network ropsten
truffle run verify Deployer --network rinkeby
truffle run verify Deployer --network kovan
truffle run verify Deployer --network mainnet

Note: ensure .deployment.js is accurately setup for the above to work.

Then create a new release:

  1. Increment the version in package.json as required, as part of a new or existing Pull Request.
  2. Once the approved PR has been merged, run git tag <version> (where <version> is same as in package.json) on the merge commit.
  3. Run git push --tags
  4. The CI server will now do a build and deploy to NPM.
  5. Once the NPM package has been published you will need to update the dependency to it in both the server and app repositories so that they both refer to the latest contract ABI when talking to the blockchain.

Business logic and smart contract structure

Actors

  • Event organiser = the creator of the event contract. There is only one per event.
  • Event admins = the people who can help the event organiser. Can do everything the organiser can do apart from transfering the contract ownership and destroying the event contract deployer contract.
  • Participants = People who RSVPed the event
  • Attendees = People who were marked as attended

Event flow

  • When the event organiser creates an event, the organiser can specify the unit of currency (ETH or ERC20), name of the event(name), amount of commitment (deposit), event capaicty (limitOfParticipants), and cooling period (cooling period) when users can withdraw their commitment.
  • The event organiser adds admins
  • Until the first participant RSVP, the event admins can change the name and deposit amount
  • The event admins can change limitOfParticipants to increase the capacity of the event.
  • When participants RSVP, they can RSVP if they call register function with the correct deposit value.
  • For events participants commit ERC20 tokens, the user has to call token.approve(deposit) prior to RSVP.
  • When user arrives to the venue, admins can check in users. This will be done off chain.
  • If the event is canceled, each participant can withdraw the deposit amount (minus gas fee)
  • Once the event is over, one of the admins call finalize sending the array of participants information in the bitmap format (to save gas).
  • Once the event is finalised, the attendees can withdraw the proportion of the sum of the deposit on the contract. For example, if the deposit is 0.1 ETH , 10 people RSVP, and 5 people turned up, then each attendee can withdraw 1/5 = 0.2 ETH.
  • If the attendees do not withdraw within cooling period, one of the admins can clear so that the remaining deposit will be send to the event organiser.

Contract files

~/.../kickback/contracts (spike-dai)$tree contracts/
contracts/
├── AbstractConference.sol
├── Conference.sol
├── Deployer.sol
├── DeployerInterface.sol
├── ERC20Conference.sol
├── ERC20Deployer.sol
├── EthConference.sol
├── EthDeployer.sol
├── GroupAdmin.sol
├── Migrations.sol
├── MyToken.sol
└── zeppelin
    ├── lifecycle
    │   └── Destructible.sol
    └── ownership
        └── Ownable.sol

  • Each even has its own contract. AbstractConference.sol is the superclass which contains all the functions.
  • The AbstractConference.sol inherits from Distructible, Ownnable and GroupAdmin.
  • ERC20Conference.sol and EthConference.sol inherits AbstractConference.sol and override logic to RSVP and withdraw.
  • Deployer.sol is a factory contract to deploy smart contract for each event.
  • Due to the gas size exceeding the block size, the actual binaries of the contracts are deployed via ERC20Deployer.sol and EthDeployer.sol and their deployed contract addresses are passed into the constructor of Deployer.sol. The Deployer.sol determines whether it should deploy ERC20 version if contract address is passed. Otherwise it will deploy the Eth version.
  • For ERC20 token contracts, we import the openzeppelin library.
$grep openzeppelin contracts/*sol
contracts/ERC20Conference.sol:import 'openzeppelin-solidity/contracts/token/ERC20/IERC20.sol';
contracts/MyToken.sol:import 'openzeppelin-solidity/contracts/token/ERC20/ERC20.sol';
contracts/MyToken.sol:import 'openzeppelin-solidity/contracts/token/ERC20/ERC20Detailed.sol';

The reason we have zeppelin/* contracts locally rather than importing from NPM is that they are as follows.

  • zeppelin/lifecycle/Destructible.sol no longer exists on openzeppelin repo
  • zeppelin/lifecycle/Ownable.sol has code changed (owner field is changed from private to public)

Some decision behind the architectural choice.

We have made certain architectual deciisions which may seem suboptimal to people who look into the code for the first time. The following section covers context behind why we made such decisions.

Deploying each contract per event.

A new contract needs to be deployed for each event, which incurs some cost at each deploy. This makes it relatively expensive to use for a small number of participant especially when the gas price or Ether price is expensive. However, this will allow us to constantly update and refactor the contract to be up to date without having complex upgradability strategy.

As our contract matures we would like to transition to a model where one contract holds multiple events and make certain logics pluggable/upgradable.

No strict time dependencies.

The current contract does not contain any information about the event start and end time as real events often do not have strict deadlines on participation. Also, the current unstable nature of Ethereum mainnet occasionally makes it difficult (or too expensive) to interact with the contract in a timely manner so it often comes down to the event owner to decide when one ends the event. The only exception is cooling period which lasts for a week by default. Having the cooling period is mainly to encourage users to withdraw deposits as soon as possible so that users can avoid possible loss of funds if any bugs or vulnerabilities are found (or the deployment account is compromised). Please refer to this blog post for the user impact when the Ethereum network is under performance pressure.

Pull over Push

The users need to interact with the smart contract twice, at registration and at withdrawal. The need to manually withdraw fund is an inconvenience to the users but this is to avoid the potential reentrancy attack. Please refer to this blog post for how this decision was made.

We are planning to transaition into a model where user commitment is always stored in their pot so that they do not have to withdraw every time the event ends

Past vulnerabilities

Please refer to BlockParty version 0.8.4 pre auditing guide.

Deployed contracts

For all the latest deployed deployer contracts, see refer to deployedAddresses.json

Mainnet deployer contract revisions

  • npm version 1.2.9 "0x3361aa92E426E052141Daf9e41A09d36e994Ba23"
  • npm version 1.2.0 "0x5cD26E6DC8200630508672fF5C726478e08Da52B" (contained a bug so aborted)
  • npm version 1.0.x "0x0F84461931866cFB2796E09B20520847D49F80F2"