Swap

Released: April 27th, 11:59pm EST
Due: May 6th, 11:59pm EST

Introduction

You've now worked across several different aspects of blockchains. First, you implemented a blockchain storage solution in Chain. Then, you added a consensus mechanism in Coin, allowing us to run a fully (mostly?) functioning cryptocurrency. Lastly, you implemented a scaling solution in Lightning.

Now, it's time to dip your toes into smart contracts and cross-chain commerce. If you haven't already completed the Solidity lab, do that first. It covers Hardhat installation, which is required for this project.

Important Concepts

Before we begin, it's worth reviewing some of the foundations for this project.

  • Escrowing: In standard finance, escrow accounts are owned by third parties to hold assets until some condition is fulfilled. In blockchain finance, those third parties are smart contracts. Putting those together, escrowing an asset on a blockchain means transferring custody to a smart contract until some condition is fulfilled.
  • Cross-Chain Commerce: Cross-chain commerce allows us to make financial exchanges across multiple blockchains that cannot communicate with each other. For example, we could have cross-chain commerce by using the incompatible Bitcoin and Ethereum chains (although any such implementation wouldn't be feature-rich, as Bitcoin lacks smart contracts)
  • Hashlocks: hashlocks are the backbone of many smart contracts. I'm free to publish H(S) as far and wide as I like, since nobody will be able to reverse-engineer S. The fulfillment condition for releasing an escrowed asset is providing S, which the contract can quickly verify hashes to H(S).

Hedging Against Sore Lose Attacks

Here's the original paper describing the protocol we'll cover below. You should read up until the start of Section 6, which is a total of just under 4 pages. If you do not, you likely will not understand the purpose of this protocol: limiting the impact of sore loser attacks.

Sore loser attack:

When one party decides to halt participation in cross-chain commerce partway through a protocol, leaving the other parties’ assets locked up for a long duration (Xue, 2021).

The Protocol

In this example, Alice sets up a cross-chain swap with Bob. She owns Apricots and he owns Bananas. She wants to trade some of her Apricots for some of his Bananas.

Some of the communication between Alice and Bob must happen off chain, such as Alice telling Bob the hashLock for the swaps. For simplicity's sake, the protocol only depicts on-chain communication, which is the part that you will be implementing.

Notation

P_a: Alice's premium amount
P_b: Bob's premium amount
A_a: Alice's asset amount
A_b: Bob's asset amount
s: the secret used to create the hash lock
H(s): the hash lock

Created with Raphaël 2.2.0If everything goes as expected...AliceAliceApricot ChainApricot ChainBanana ChainBanana ChainBobBob(1) setup BananaSwap with H(s)(2) setup ApricotSwap with H(s)Entering premium escrowing phase(3) escrow P_a + P_b(4) escrow P_bEntering asset escrowing phase(5) escrow A_a(6) escrow A_bEntering redemption phase(7) redeem A_b with s & refund P_a + P_b(8) redeem A_a with s & refund P_b

Steps Explained

(1 & 2) Alice must first set up the swap contracts. She will create a BananaSwap on the BananaChain and an ApricotSwap on the ApricotChain. Both will have the same hashLock H(S), but only Alice knows the secret S.

Note: A participant in this protocol escrows their premium on the chain of the asset they'd like to receive. That means that Alice and Bob must each own both assets. In this case, that means they both must own some Apricot and some Banana.

(3) As the initiator, Alice has to escrow an amount equal to her premium + Bob's premium on the BananaChain. This might surprise you, as we'd expect her to only have to pay the amount totalling her premium. The reason for this is covered in Section 5.2 of the original paper.

(4) Having seen Alice successfully escrow her premium on the BananaChain, Bob now escrows his premium on the ApricotChain.

(5) Now that both parties have escrowed their premiums, we enter the asset escrowing phase. If either party disappears at this point, they'll lose their premium! Alice escrows her Apricots on the ApricotChain.

(6) Having seen Alice correctly escrow her Apricots, Bob follows suit by escrowing his Bananas on the BananaChain.

(7) Alice sees that Bob's bananas are correctly escrowed, so now she gets to redeem them! Doing so requires her to reveal the secret S. She should also get her premium refunded, since she followed the specifications of the protocol.

(8) Since Alice had to reveal S to collect her Bananas, Bob now knows the secret! Using S, he can collect his Apricots and have his premium P_b refunded.

FIN.

Created with Raphaël 2.2.0If Bob disappers...AliceAliceApricot ChainApricot ChainBanana ChainBanana ChainBobBob(1) setup BananaSwap with H(s)(2) setup ApricotSwap with H(s)Entering premium escrowing phase(3) escrow P_a + P_b(4) escrow P_bEntering asset escrowing phase(5) escrow A_a(6) ZZZ ZZZ ZZZEntering redemption phase(7) redeem P_b & refund A_a(8) refund P_b + P_a

Steps Explained

(1-5) Ommited. See explanations above.

(6) Bob is nowhere to be found. He must be sleeping

(7) Since Bob missed his deadline to escrow his asset, Alice collects his premium deposited on the ApricotChain. She will also get her asset refunded.

(8) Alice must also get her own premium refunded, which was escrowed on the BananaChain

FIN.

Created with Raphaël 2.2.0Bob never escrows his premium...AliceAliceApricot ChainApricot ChainBanana ChainBanana ChainBobBob(1) setup BananaSwap with H(s)(2) setup ApricotSwap with H(s)Entering premium escrowing phase(3) escrow P_a + P_b(4) ZZZ ZZZEntering redemption phase(5) refund P_b + P_a

(1-3) Ommited. See explanations above.

(4) Bob is nowhere to be found. He must be sleeping

(5) Shoot! Alice doesn't get anything from this interaction. All she can do is get her initial premium refunded. She won't be doing any business with Bob again

FIN.

Assignment

Important caveat:

The protocol above uses two distinct chains, but we're only using Solidity and the Ethereum chain for this project. Ethereum has the best testing support, and it prevents you from having to learn multiple languages for this project.

We'll be implementing all of the various calls above, since a person could be either the initiator or follower on either chain.

While in theory you're implementing a cross-chain swap, in reality you're implementing an ERC20 swap with a protocol that would work across distinct chains.

For this assignment, you will implement the functions listed below. We recommend that you tackle them in the following order:

  1. setup
  2. escrowPremium
  3. escrowAsset
  4. redeemAsset
  5. refundAsset
  6. redeemPremium
  7. refundPremium

All functions are worth (roughly) the same amount. Check out our grading breakdown for more info.

Some general hints:

  • We recommend that you put all of your require statements in a modifier, which is currently blank.
  • Remember that an event should be emitted after a function executes.

Implement:

/** setup is called to initialize an instance of a swap in this contract. Due to storage constraints, the various parts of the swap are spread out between the three different mappings above: swaps, assets, and premiums. */ function setup( uint expectedAssetEscrow, uint expectedPremiumEscrow, address payable assetEscrower, address payable premiumEscrower, address assetName, bytes32 hashLock, uint startTime, bool firstAssetEscrow, uint delta ) public payable canSetup(hashLock) {}

Overview

  • This function instantiates an instance of a swap (for one side of the protocol).
  • You should prevent initiating duplicate swap by setting certain requirements. (hint: you can use zero address as "null")
  • You'll want to instantiate the appropriate structs based on the arguments and then add those structs to their mappings.
  • So what are the deadlines/timeouts that we have to set? That depends on whether that chain has the first asset escrow. The delta argument is an agreed-upon safe time interval for both parties, such that each interval is some n * delta, beginning from the startTime.
  • In the first protocol sequence above, firstAssetEscrow is true on the ApricotChain and false on the BananaChain (since in the asset escrowing round the Apricots are escrowed prior to the Bananas). Thus, the deadline for the premium on the BananaChain (where firstAssetEscrow is false) is startTime + 1 * delta.
  • The timeout for the swap is a time after which all possible events have occurred on a chain. Hint: look at the ApricotChain.

(Somewhat Hidden) Helpful Fields:

  • msg.sender: the function's caller.

Implement:

/** The premium escrower has to escrow their premium for the protocol to succeed. */ function escrowPremium(bytes32 hashLock) public payable canEscrowPremium(hashLock) {}

Hint

  • Before you begin, you should require a few things to be true.
  • Set relevant field(s) in this swap's premium struct demonstrating you've taken this step.

Helpful Functions

  • ERC20's transfer or transferFrom? Go back to HW 3 for a refresher.
  • ERC20's balanceOf

Implement:

/** The asset escrower has to escrow their premium for the protocol to succeed. */ function escrowAsset(bytes32 hashLock) public payable canEscrowAsset(hashLock) {}

Hint

  • Don't forget requirements!

Implement:

/** redeemAsset redeems the asset for the new owner. */ function redeemAsset(bytes32 preimage, bytes32 hashLock) public canRedeemAsset(preimage, hashLock) {}

Hint

  • How do we make sure if a token is being moved properly and reflected in structs?

Helpful Functions

  • sha256(abi.encode(message)): how to get the SHA-256 hash of message

Implement:

/** refundPremium refunds the premiumEscrower's premium should the counterparty break from the protocol */ function refundPremium(bytes32 hashLock) public canRefundPremium(hashLock) {}

Overview

  • Ensure the order of the protocols.

Implement:

/** refundAsset refunds the asset to its original owner should the swap fail */ function refundAsset(bytes32 hashLock) public canRefundAsset(hashLock) {}

Implement:

/** redeemPremium allows a party to redeem the counterparty's premium should the swap fail */ function redeemPremium(bytes32 hashLock) public canRedeemPremium(hashLock) {}

Testing

This assignment is autograded, and you are able to run our test suite as many time as you like. While you can run all of your tests on Gradescope, we strongly recommend that you test locally. That way, you can fail quickly and try again!

Hardhat allows us to log solidity variables, something that you cannot do normally. This means that you can use console.log(message) in your .sol files to log a message to standard output. This will save you a lot of frustration!

Testing Resources:

Install

This assignment requires a few steps prior to cloning the stencil. Please make sure you do those first!

  1. Clone the stencil repo.
  2. To install the package dependencies, run npm i
  3. Get after it! Good luck :smiley:

HandIn

  • Log into Gradescope and Submit your code using GitHub.

Grading

Note: This assignment's feedback form is a REQUIRED part of the assignment, worth 5 points.

The form can be found here.

This assignment is out of 100 points.

On Gradescope, each individual test is worth 1 point, for 38 total points. It was much easier to set up that way, so we'll adjust the scores later to match the rubric below.

Rubric

Test Suite Points
Setup 14
Escrow Premium 13
Escrow Asset 14
Redeem Asset 14
Refund Asset 14
Redeem Premium 13
Refund Premium 13
Feedback Form 5
Total 100