Released: April 27th, 11:59pm EST
Due: May 6th, 11:59pm EST
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.
Before we begin, it's worth reviewing some of the foundations for this project.
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).
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.
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
(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
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
(8) Since Alice had to reveal
FIN.
(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.
(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.
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:
setup
escrowPremium
escrowAsset
redeemAsset
refundAsset
redeemPremium
refundPremium
All functions are worth (roughly) the same amount. Check out our grading breakdown for more info.
Some general hints:
/**
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)
{}
delta
argument is an agreed-upon safe time interval for both parties, such that each interval is some n * delta
, beginning from the startTime
.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
.msg.sender
: the function's caller.
/**
The premium escrower has to escrow their premium for
the protocol to succeed.
*/
function escrowPremium(bytes32 hashLock)
public
payable
canEscrowPremium(hashLock)
{}
transfer
or transferFrom
? Go back to HW 3 for a refresher.balanceOf
/**
The asset escrower has to escrow their premium for
the protocol to succeed.
*/
function escrowAsset(bytes32 hashLock)
public
payable
canEscrowAsset(hashLock)
{}
/**
redeemAsset redeems the asset for the new owner.
*/
function redeemAsset(bytes32 preimage, bytes32 hashLock)
public
canRedeemAsset(preimage, hashLock)
{}
sha256(abi.encode(message))
: how to get the SHA-256 hash of message
/**
refundPremium refunds the premiumEscrower's premium
should the counterparty break from the protocol
*/
function refundPremium(bytes32 hashLock)
public
canRefundPremium(hashLock)
{}
/**
refundAsset refunds the asset to its original owner
should the swap fail
*/
function refundAsset(bytes32 hashLock)
public
canRefundAsset(hashLock)
{}
/**
redeemPremium allows a party to redeem the
counterparty's premium should the swap fail
*/
function redeemPremium(bytes32 hashLock)
public
canRedeemPremium(hashLock)
{}
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!
This assignment requires a few steps prior to cloning the stencil. Please make sure you do those first!
npm i
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.
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 |