Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

US Treasury Auction Example #12

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions examples/us-treasury-auctions/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# US Treasury Auction Example

This example demonstrates how the US Treasury could use SUAVE to run their auctions.

While efficient and confidential execution is not an issue the Treasury has, we feel they might benefit from verifiability after each auction. For instance, they may be able to more readily prove that [hacks of the ICBC](https://twitter.com/jameslavish/status/1724541469476991139) did indeed result in lower participation, if that was actually the case.

This example provides support for multiple types of auctions: TBills, Notes, Bonds, TIPS and FRNs could all potentially be run as different `auctionType`s using this same contract. Please read [this thread](https://twitter.com/jameslavish/status/1577334009092198400) for an explanation of each.

Anyone can use this contract to run auctions - there are no special privileges. We assume that Treasury would specify the auction that they create and direct traders and other relevant parties to them.

This example does not move securities once the auction is settled: it simply runs the auction itself. We assume Treasuries would distribute the relevant securities based on the verifiable auction results once it has been run.

For on-chain assets, you might wish to write another contract the handles the distribution of whatever has been auctioned immediately and without intermediation once the auction has been completed.

## How to use

Run `Suave` in development mode:

```
cd suave-geth
suave --suave.dev
```

Execute the deployment script:

```
go run main.go
```

147 changes: 147 additions & 0 deletions examples/us-treasury-auctions/TAuction.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;

import "../../suave-geth/suave/sol/libraries/Suave.sol";

contract TAuction {
struct Auction {
uint256 id;
string auctionType;
address creator;
uint64 startBlock;
uint64 endBlock;
uint256 totalAmount;
uint16 coupon;
}

struct Bid {
uint256 amount;
uint16 rate;
}

Auction[] public auctions;
mapping(uint256 => Suave.BidId[]) private _bids;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this used?


event AuctionCreated(uint256 indexed auctionId, string auctionType, address indexed creator, uint256 startBlock, uint256 endBlock, uint16 coupon);
event AuctionCompleted(uint256 indexed auctionId, uint256 btc, uint16 stopRate);
event BidSubmitted(uint256 indexed auctionId, Suave.BidId bidId);

// Create a new auction
function createAuction(string memory auctionType, uint256 totalAmount, uint64 duration, uint8 coupon) external returns (uint256) {
uint256 auctionId = auctions.length;
auctions.push(Auction(auctionId, auctionType, msg.sender, block.number, block.number + duration, totalAmount, coupon));
emit AuctionCreated(auctionId, auctionType, msg.sender, uint64(block.number), uint64(block.number + duration), coupon);
return auctionId;
}

// Submit a bid (confidentially)
// the decryption condition will be the startBlock
// TODO: why are decryption conditions uint64s, but often we use block numbers, which are generally uint256?
function submitBid(address[] memory bidAllowedPeekers, address[] memory bidAllowedStores, uint64 blockHeight) external payable {
require(Suave.isConfidential(), "Execution must be confidential");

bytes memory confidentialInputs = Suave.confidentialInputs();
(uint256 auctionId, uint256 amount, uint16 rate) = abi.decode(confidentialInputs, (uint256, uint256, uint16));

require(auctionId < auctions.length, "Invalid auction ID");
require(auctions[auctionId].startBlock > block.number, "Auction yet to start");
require(auctions[auctionId].endBlock < block.number, "Auction ended");

if (rate == 0) {
// Store non-competitive bid
Suave.Bid memory bid = Suave.newBid(auctions[auctionId].startBlock, bidAllowedPeekers, bidAllowedStores, "tauction:v0:noncompetitive");
Suave.confidentialStore(bid.id, "tauction:v0:noncompetitive", confidentialInputs);
// Calculate current total and store for later
bytes memory totalData = Suave.confidentialRetrieve(bid.id, "tauction:v0:noncompetitivetotal");
uint256 currentTotal = totalData.length > 0 ? abi.decode(totalData, (uint256)) : 0;
uint256 newTotal = currentTotal + amount;
Suave.confidentialStore(bid.id, "tauction:v0:noncompetitivetotal", abi.encode(newTotal));
emit BidSubmitted(auctionId, bid.id);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the current iteration, events cannot be emitted off-chain

} else {
// Store competitive bids, in order of rate offered
Suave.Bid memory bid = Suave.newBid(auctions[auctionId].startBlock, bidAllowedPeekers, bidAllowedStores, "tauction:v0:competitive");
Suave.Bid[] memory allCompetitiveSortedBids = Suave.fetchBids(blockHeight, "tauction:v0:competitiveSorted");

// TODO: I want to store competitive bids in ascending order, where
// 1 is the lowest rate, 2 is slightly higher etc.
// overwriting ids won't work, and I am not sure how to add a new field to the confidentialInputs

// no sorted bids yet - goes in at rank 1
if (allCompetitiveSortedBids.length == 0) {
Suave.confidentialStore(1, "tauction:v0:competitiveSorted", confidentialInputs);
}
// some bids in the sorted array, need to insert new one
bool inserted = false;
uint i = 0;

for (; i < allCompetitiveSortedBids.length; i++) {
// TODO: is this the correct way to retrieve the rate stored for any given bid?
uint16 rateToCompare = abi.decode(Suave.confidentialRetrieve(i, "tauction:v0:competitiveSorted"), (uint16));

if (rate < rateToCompare) {
// Insert the current bid here and shift the others
shiftAndStoreBids(i, allCompetitiveSortedBids.length, confidentialInputs);
Suave.confidentialStore(i, "tauction:v0:competitiveSorted", confidentialInputs);
inserted = true;
break;
}
}

if (!inserted) {
// If the current bid's rate is higher than all, insert at the end
Suave.confidentialStore(allCompetitiveSortedBids.length, "tauction:v0:competitiveSorted", confidentialInputs);
}

emit BidSubmitted(auctionId, bid.id);
}
}

// Complete an auction and emit results
// Anyone can call this, as the creator sets the duration
// question: can/should it emit all bids for public verification?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does not matter if you emit the bids, they are in the confidential store so they are not public. Normally, the pattern is to use the onchain callback to return the leak data of the bid that you want to expose.

function completeAuction(uint256 auctionId) external {
require(auctionId < auctions.length, "Invalid auction ID");
require(auctions[auctionId].endBlock < block.number, "Auction not complete");

(uint256 btc, uint16 stopRate) = calculateAuctionOutcome(auctionId);
emit AuctionCompleted(auctionId, btc, stopRate);
}


// Calculate the btc and stop rate
function calculateAuctionOutcome(uint256 auctionId) private view returns (uint256 btc, uint256 stopRate) {
Auction memory auction = auctions[auctionId];
bytes memory nonCompetitiveTotalData = Suave.confidentialRetrieve(auction.startBlock, "tauction:v0:noncompetitivetotal");
uint256 nonCompetitiveTotal = nonCompetitiveTotalData.length > 0 ? abi.decode(nonCompetitiveTotalData, (uint256)) : 0;

uint256 remainingAmount = auction.totalAmount - nonCompetitiveTotal;
uint256 totalCompetitiveBidAmount = 0;
bool stopRateSet = false;

Suave.Bid[] memory competitiveBids = Suave.fetchBids(auction.startBlock, "tauction:v0:competitiveSorted");
for (uint i = 0; i < competitiveBids.length; i++) {
(uint256 bidAmount, uint16 bidRate) = abi.decode(Suave.confidentialRetrieve(competitiveBids[i].id, "tauction:v0:competitive"), (uint256, uint16));
totalCompetitiveBidAmount += bidAmount;

if (!stopRateSet && remainingAmount > 0) {
if (remainingAmount <= bidAmount) {
stopRate = bidRate;
stopRateSet = true;
}
remainingAmount -= bidAmount;
}
}

btc = (nonCompetitiveTotal + totalCompetitiveBidAmount) / auction.totalAmount;
return (btc, stopRate);
}

function shiftAndStoreBids(uint startIdx, uint length, bytes memory currentBidData) private {
for (uint j = length; j > startIdx; j--) {
bytes memory bidDataToShift = Suave.confidentialRetrieve(j - 1, "tauction:v0:competitiveSorted");
Suave.confidentialStore(j, "tauction:v0:competitiveSorted", bidDataToShift);
}
Suave.confidentialStore(startIdx, "tauction:v0:competitiveSorted", currentBidData);
}

}
10 changes: 10 additions & 0 deletions examples/us-treasury-auctions/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package main

import (
"github.com/flashbots/suapp-examples/framework"
)

func main() {
fr := framework.New()
fr.DeployContract("TAuction.sol/TAuction.json")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you expand the example to also send the transactions?

}
Loading