Erasure Smart Contracts
A decentralized marketplace for data feeds and predictions
Setup
This repo assumes you have truffle installed globally. If you don't have it make sure you have the most recent version installed.
yarn global add truffle
truffle version
Truffle v4.1.7 (core: 4.1.7)
Solidity v0.4.23 (solc-js)
Install packages using yarn
yarn
Start a local blockchain like Ganache. You can use Ganache CLI or the desktop client.
yarn run ganache
Compile and migrate your local smart contracts.
truffle migrate --reset
Testing
yarn run ganache
yarn run test
Rough Spec
Contracts
There are 3 main contracts:
- Erasure - a simple contract that handles time-locked (or lagged) messages
- Marketplace - the contract that handles the creation and the purchase of the data prediction feeds. Under the hood, the marketplace contract uses the Erasure contract to create the prediction.
- ERASE - the token that powers the staking in the marketplace contract
ERASE Token Spec
The exact details of the token spec is TBD. But fundamentally, ERASE will be a burnable and mintable ERC20 contract.
Erasure Contract Spec
Messages
createMessage(uint256 nonce, string ipfsHash, uint256 revealedBy)
revealMessage(uint256 nonce, bytes32[] secret)
Events
event MessageCreation(uint256 nonce, address creator);
event MessageReveal(uint256 nonce);
Marketplace Contract Spec
Configuration
configureMinimumStake(uint256 minimumStake)
=> updates minimumStake during the configuration phase of the contractconfigureGriefingRatio(uint256 stakeRatio)
=> updated griefingRatio (used for griefing) during the configuration phase of the contractconfigureGriefingPeriod(uint256 griefingPeriodInWeeks)
Feeds
createFeed(uint256 stakeToCreate, uint256 feePerPrediction)
cancelFeed(uint256 feedId)
=> can only be called if the feed doesn't have a buyer or if the feed is not in a griefing period. This also returns the seller stake.endBuyerSellerRelationship(uint256 feedId)
=> can only be called by the seller or buyer. Unsets the buyer. 2 weeks following this, buyer or seller can slash stakes.stakeToBuyFeed(uint256 feedId, uint256 stakeToBuy)
=> sets buyerAddress and escrows the stake, can only be called if buyerAddress of a given feed is unsetgriefBuyer(uint256 feedId, address buyer, uint256 amount)
, can only be called by the seller for a specific feed within 2 weeks of ending relationshipgriefSeller(uint256 feedId, uint256 amount)
, can only be called by the buyer for a specific feed within 2 weeks of ending relationshipupdateFeePerPrediction(uint256 feedId, uint256 feePerPrediction)
=> only if feed doesn't have a buyer.withdrawBuyerStake(uint256 feedId)
=> after the griefing period is over, the buyer can withdraw the leftover stake from the contract
Predictions
createPrediction(uint256 feedId, uint256 nonce, string ipfsHash, uint256 revealedBy)
=> calls the Erasure contract to create the corresponding message object.revealPredictionToBuyer(uint256 predictionId, bytes32[] buyerSecret)
revealPredictionToPublic(uint256 predictionId, bytes32[] publicSecret)
=> calls the Erasure contract to update the secret
of the corresponding message object.purchasePrediction(uint256 predictionId)
Users
createUser(string publicKey)
=> Adds an address/publicKey pair to userAddressToPublicKey mapping, only for new Ethereum addresses.
Events
event BuyerStakeWithdrawal(address buyer, uint256 buyerStake);
event FeedBuyerRemoval(uint256 feedId, address seller, address oldBuyer, address actor);
event FeedCancellation(uint256 feedId, address seller);
event FeedCreation(uint256 feedId, address seller);
event FeedFeePerPredictionUpdate(uint256 feedId, address seller, uint256 feePerPrediction);
event FeedPurchase(uint256 feedId, address seller, address buyer);
event GriefByBuyer(address buyer, address seller, uint256 buyerStake, uint256 sellerStake);
event GriefBySeller(address buyer, address seller, uint256 buyerStake, uint256 sellerStake);
event PredictionBuyerReveal(uint256 feedId, uint256 predictionId, address seller, address buyer);
event PredictionCreation(uint256 feedId, uint256 predictionId, address seller);
event PredictionPublicReveal(uint256 feedId, uint256 predictionId, address seller);
event PredictionPurchase(uint256 feedId, uint256 predictionId, address seller, address buyer);
event UserCreation(address userAddress, string publicKey);
ERASE Token Spec
Token Functionality
approveAll(address spender)
=> Sets the allowance from msg.sender to spender to 2 ^ 256 - 1transferFrom(address from, address tto, uint value)
=> Modified version of the standard transferFrom. If the allowance is equal to 2 ^ 256 - 1, it doesn't adjust the allowance post transfer
Testing Functionality
- buyTokensForTesting() => Current contract is set to give 1000 ERASE tokens for 1 ETH. This method receives ETH and mints tokens to the msg.sender.
- distributeTokensForTesting(address[] accounts, uint256 amount) => This can only be called by the contract owner to mint tokens to an array of accounts
- returnTokensForTesting(uint256 amount) => This is the reverse of buyTokensForTesting(). When the user sends ERASE tokens, they get ETH back (if they actually own the ERASE tokens). The ERASE tokens are in turn burned.
Events
event ApproveAll(address sender, address spender);
event TestingBuyTokens(address account, uint256 amount);
event TestingDistributeTokens(address account, uint256 amount);
event TestingReturnTokens(address account, uint256 amount);
TODOS
v0.0.4
v0.0.3
v0.0.2
v0.0.1
Griefing Spec v0.1
- 3 to 1 ratio in slashing.
- You can only slash stakes of people you’ve been in a “buyer-seller” relationship with
- Once you end a relationship, you have 2 weeks to slash stakes.
Simplifying assumptions
- Staking factors are 1 to 3 and 3 to 1 per our discussion on Sunday (ie buyer can burn 1 token to burn 3 tokens of the seller and vice versa). Minimum stake is 10 tokens.
- Stake is not only an indicator of confidence, but also an indicator of good business
- One buyer per feed, no way to out-bid someone. The relationship needs to be explicitly terminated.
- Structure of the file is formatted, with a nonce added to increase entropy
- feePerFile is constant for a given feed if there is a buyer. Otherwise, the fee can be adjusted.
Questions
- What type of upgradability options are available?
- ERC20 tokens require an approve() call, before a transfer() is possible. That’s horrible UX. What is the best way to avoid this to make a single call? Current options, we’ve explored are:
- Use a standard like ERC827. This has an approveAndCall() method that let’s us do what we want. However, according to OZ, there are some security issues.
- Modify our ERC20 implementation to directly interface with the Erasure protocol.
- Or this.
- How much thought should we put in to designating state variables as public vs. private vs. internal?
- What if a prediction is revealed first on the Erasure message? How should the marketplace contract behave?
- Currently, we use
uint64
for IDs, while most standard Ethereum apps seem to use uint256
, is that a problem?
Deploying to Rinkeby, Mainnet
To deploy to rinkeby or mainnet, first configure your environment.
Generate a MNEMONIC using Metamask and get an API key from Infura
Make sure your account (the first address derived from your MNEMONIC) has at least 0.7 ETH
, then run
MNEMONIC="your mnemonic" INFURA_API_KEY="your API key" yarn run migrate:rinkeby
MNEMONIC="your mnemonic" INFURA_API_KEY="your API key" yarn run migrate:mainnet