Skip to content

statechannels/speedrun-statechannels

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ— scaffold-eth | 🏰 BuidlGuidl

🚩 Challenge N: A state channel application

🐌 The Ethereum blockchain has great decentralization & security properties. These properties come at a price: transaction throughput is low, and transactions can be expensive (search term: blockchain trilemma). This makes many traditional web applications infeasible on a blockchain... or does it?

🍰 A number of approaches to scaling have been developed, collectively referred to as layer-2s (L2s). Among them is the concept of payment channels, state channels, and state channel networks. This tutorial walks through the creation of a simple state channel application, where users seeking a service lock collatoral on-chain with a single transaction, interact with their service provider entirely off-chain, and finalize the interaction with a second on-chain transaction.

πŸ§‘β€πŸ€β€πŸ§‘ State channels really excel as a scaling solution in cases where a fixed set of participants want to exchange value-for-service at high frequency. The canonical example is in file sharing or media streaming: the server exchanges chunks of a file in exchange for micropayments.

πŸ§™ In our case, the service provider is a Guru who provides off-the-cuff wisdom to each client Rube through a one-way chat box. Each character of text that is delivered is expected to be compensated with a payment of 0.001 ETH.

We will:

  • πŸ›£οΈ Build a Streamer.sol contract that collects ETH from numerous client addresses using a payable fundChannel() function and keeps track of balances.
  • πŸ’΅ Exchange paid services off-chain between the Streamer.sol contract owner (the Guru) and rube clients with funded channels. The Guru provides the service in exchange for signed vouchers which can later be redeemed on-chain.
  • ⏱ Create a Challenge mechanism with a timeout, so that rubes are protected from a Guru who goes offline while funds are locked on-chain (either by accident, or as a theft attempt).
  • ⁉ Consider some security / usability holes in the current design.

// todo

🌟 The final deliverable is deploying a Dapp that lets users send ether to a contract and stake if the conditions are met, then yarn build and yarn surge your app to a public webserver. Submit the url on SpeedRunEthereum.com!

// todo

πŸ’¬ Meet other builders working on this challenge and get help in the Challenge 1 telegram!


Checkpoint 0: πŸ“¦ install πŸ“š

git clone https://github.com/statechannels/speedrun-statechannels.git challenge-N-statechannels

cd challenge-N-statechannels

yarn install

Files that we'll be editing in this tutorial are:

  • 00_deploy_streamer.js in packages/hardhat/deploy
  • Streamer.sol in packages/hardhat/contracts
  • App.jsx in packages/react-app/src

πŸ” Tip: entry points for each of the checkpoints that involve writing code can be located by searching these files for Checkpoint N (for whatever N value)


Checkpoint 1: πŸ”­ Environment πŸ“Ί

You'll have three terminals up for:

yarn start   (react app frontend)
yarn chain   (hardhat backend)
yarn deploy  (to compile, deploy, and publish your contracts to the frontend)

⚠️ Note: yarn deploy will report an unexpected error. We will fix this in Checkpoint 2 and redeploy.

πŸ’» View your frontend at http://localhost:3000/

πŸ‘©β€πŸ’» Rerun yarn deploy --reset whenever you want to deploy new contracts to the frontend.


Checkpoint 2: Configure Deployment & Wallets

Like the token vendor challenge, we'll be building an Ownable contract. The contract owner is the Guru - the service provider in this application, and you will use multiple browser windows or tabs to assume the roles of Guru and rube (service provider & client).

πŸ‘ contract Streamer inherits Ownable with the is keyword. Ownable comes from openzeppelin-contracts - a collection of high quality smart contract library code.

πŸ“ In packages/hardhat/deploy/00_deploy_streamer.js, supply an address from your frontend wallet to the streamer.transferOwnership() function.

You'll have to redeploy with yarn deploy --reset.

We'll need another active address to act as the rube in our app. To do this,

  • open localhost:3000 in a new tab / window of the current browser
  • click the wallet icon (top right) to open the wallet
  • private key -> generate will reload the page under a new address

The wallet icon now lets you move between accounts. Eventually you'll probably want a few wallets & windows open simultaneously.

(Note: previous challenges created alt addresses by opening the page with a private window or a different browser. This will not work for this challenge, because the off-chain application uses a very simple communication pipe that doesn't work between different browsers or private windows.)

πŸ₯… Goals

  • does your original frontend address recieve the Hello Guru UI?
  • do your alternate addresses recieve the Hello Rube UI?

Checkpoint 3: Fund a channel

Like the decentralized staking challenge, we'll track balances for individual channels / users in a mapping:

mapping (address => uint256) balances;

Rubes seeking wisdom will use a payable fundChannel() function, which will update this mapping with the supplied balance.

πŸ“ Edit Streamer.sol to complete the fundChannel() function

πŸ‘ Check App.jsx to see the frontend hook for this function. (ctrl-f fundChannel)

πŸ₯… Goals:

  • does opening a channel (from Rube's tab, you may need some funds from the faucet) cause a Recieved Wisdom box to appear?
  • do opened channels appear on the guru's UI as well?
  • using the Debug Contracts tab, does a repeated call to fundChannel fail?

Checkpoint 4: Exchange the Service

Now that the channel is funded, and all participants have observed the funding via the emitted event, we can begin our off-chain exchange of service. We are now working in packages/react-app/src/App.jsx.

Functions of note:

  • provideService: the guru sends wisdom over the wire to the client
  • reimburseService: the rube creates a voucher for the recieved service, signs it, and returns it
  • processVoucher: the service provider recieves and stores vouchers

The first two are complete - we will work on processVoucher, where the service provider examines returned payments, confirms their authenticity, and stores them.

πŸ“ Edit App.jsx to complete the processVoucher() function and secure this off-chain exchange. You'll need to recreate the encoded message that the client has signed, and then verify that the received signature was in fact produced by the client on that same data.

πŸ₯… Goals:

  • secure your service! Validate the incoming voucher & signature according to instructions inside processVoucher(v)
  • with an open channel, start sending advice. Can you see the claimable balance update as service is rendered?

βš”οΈ Side Quest:

  • can provideService be modified to prevent continued service to clients who don't keep up with their payments? (hint: you'll want to compare the size of your best voucher against the size of your provided wisdom. If there's too big a discrepency, cut them off!)

Checkpoint 5: Recover Service Provider's Earnings

Now that we've collected some vouchers, we'd like to redeem them on-chain and move funds from the Streamer contract's balances map to the Guru's own address. The withdrawEarnings function of Streamer.sol takes a voucher (balance + signature) as input, and should:

  • recover the signer using ecrecover() on the prefixedHashed message and supplied signature
    • Hint: ecrecover takes the signature in its decomposed form with v,,r, ands values. The string signature produced in App.jsx is just a concatenation of these values, which we split to create the on-chain friendly signature with ethers.utils.splitSignature
  • check that the signer has a running channel with balance greater than the voucher's updatedBalance
  • calculate the payout (balances[signer] - updatedBalance)
  • update the channel balance
  • pay the contract owner

Reminders:

  • changes to contracts must be redeployed to the local chain with yarn deploy --reset.
  • for troubleshooting / debugging, your contract can use hardhat's console.log, which will print to your console running the chain

πŸ“ Edit Streamer.sol to complete the withdrawEarnings() function as described

πŸ“ Edit App.jsx to enable the UI button for withdrawals

πŸ₯… Goals:

  • Recover funds on-chain for services rendered! After the guru submits a voucher to chain, you should be able to see the wallet's ETH balance increase.

βš”οΈ Side Quest:

  • withdrawEarnings is a function that only the service provider would be interested in calling. Should it be marked onlyOwner? (the onlyOwner modifier makes a function accessible only to the contract owner - anyone else who tries to call it will be immediately rejected).

Checkpoint 6: Challenge & Close the channel

So far so good:

  • rubes can connect to the Guru via an on-chain deposit
  • the pair can then transact off-chain with high throughput
  • the Guru can recover earnings with their received vouchers

But what if a rube is unimpressed with the service, and wishes to close a channel and recover whatever funds remain? What if the guru is a no-show after the initial channel funding deposit?

A payment channel is a cryptoeconomic protocol - care needs to be taken so that everyone's financial interests are protected. We'll implement a two step challenge and close mechanism that allows rubes to recover unspent funds, while keeping the guru's earnings safe.

πŸ“ Edit Streamer.sol to create a public challengeChannel() function

πŸ“ Edit App.jsx to enable the challenge and closure buttons for service clients

The challengeChannel() function should:

  • check in the balances map that a channel is already open in the name of msg.sender
  • declare this channel to be closing by setting canCloseAt[msg.sender] to block.timestamp + 30 seconds
  • emit a Challenged event with the sender's address

The emitted event gives notice to the Guru that the channel will soon be emptied, so they should apply whatever vouchers they have before the timeout period ends.

πŸ“ Edit Streamer.sol to create a public defundChannel() function

The defundChannel() function should:

  • check that msg.sender has a closed channel, by ensuring a non-zero canCloseAt[msg.sender] is lower than the current timestamp
  • transfer balances[msg.sender] to the sender
  • emit a Closed event

πŸ₯… Goals:

  • Launch a challenge as a channel client. The guru's UI should show an alert via their Cash out latest voucher button.
  • Recover the guru's best voucher before the channel closes.
  • Close the channel and recover rube funds.

βš”οΈ Side Quests:

  • Currently, the service provider has to manually submit their vouchers after a challenge is registered on chain. Should their channel wallet do that automatically? Can you implement that in this application?
  • Suppose some rube enjoyed their first round of advice. Is it safe for them to open a new channel with Streamer.sol? (Hint: what data does the guru still hold?)

⚠️ Test it!

// todo: write tests

  • Now is a good time to run yarn test to run the automated testing function. It will test that you hit the core checkpoints. You are looking for all green checkmarks and passing tests!

Checkpoint 5: 🚒 Ship it 🚁

πŸ“‘ Edit the defaultNetwork to your choice of public EVM networks in packages/hardhat/hardhat.config.js

πŸ‘©β€πŸš€ You will want to run yarn account to see if you have a deployer address

πŸ” If you don't have one, run yarn generate to create a mnemonic and save it locally for deploying.

⛽️ You will need to send ETH to your deployer address with your wallet.

πŸ“ If you plan on submitting this challenge, be sure to set your deadline to at least block.timestamp + 72 hours

πŸš€ Run yarn deploy to deploy your smart contract to a public network (selected in hardhat.config.js)


Checkpoint 6: 🎚 Frontend πŸ§˜β€β™€οΈ

πŸ“ Edit the targetNetwork in App.jsx (in packages/react-app/src) to be the public network where you deployed your smart contract.

πŸ’» View your frontend at http://localhost:3000/

πŸ“‘ When you are ready to ship the frontend app...

πŸ“¦ Run yarn build to package up your frontend.

πŸ’½ Upload your app to surge with yarn surge (you could also yarn s3 or maybe even yarn ipfs?)

😬 Windows users beware! You may have to change the surge code in packages/react-app/package.json to just "surge": "surge ./build",

βš™ If you get a permissions error yarn surge again until you get a unique URL, or customize it in the command line.

πŸ“ you will use this deploy URL to submit to SpeedRunEthereum.com.

πŸš” Traffic to your url might break the Infura rate limit, edit your key: constants.js in packages/ract-app/src.


Checkpoint 7: πŸ“œ Contract Verification

Update the api-key in packages/hardhat/package.json file. You can get your key here.

Screen Shot 2021-11-30 at 10 21 01 AM

Now you are ready to run the yarn verify --network your_network command to verify your contracts on etherscan πŸ›°


πŸƒ Head to your next challenge here.

πŸ’¬ Problems, questions, comments on the stack? Post them to the πŸ— scaffold-eth developers chat