Contracts
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()
const contract = TruffleContract(Deployer)
contract.setProvider(web3.currentProvider)
const deployer = await contract.deployed()
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 and Infura 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
Note: ensure .deployment.js
is accurately setup for the above to work.
Then create a new release:
- Increment the
version
in package.json
as required, as part of a new or existing Pull Request. - Once the approved PR has been merged, run
git tag <version>
(where <version>
is same as in package.json
) on the merge commit. - Run
git push --tags
- The CI server will now do a build and deploy to NPM.
- 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 repozeppelin/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.