From 77cce544fc5404b5a40927dfeb4ad3af71a2fe90 Mon Sep 17 00:00:00 2001 From: andytudhope <13001517+andytudhope@users.noreply.github.com> Date: Sun, 19 Nov 2023 12:28:17 +0200 Subject: [PATCH] Adds v0 treasury auction contract, still WIP --- examples/us-treasury-auctions/README.md | 29 ++++ examples/us-treasury-auctions/TAuction.sol | 147 +++++++++++++++++++++ examples/us-treasury-auctions/main.go | 10 ++ 3 files changed, 186 insertions(+) create mode 100644 examples/us-treasury-auctions/README.md create mode 100644 examples/us-treasury-auctions/TAuction.sol create mode 100644 examples/us-treasury-auctions/main.go diff --git a/examples/us-treasury-auctions/README.md b/examples/us-treasury-auctions/README.md new file mode 100644 index 0000000..59ba0a9 --- /dev/null +++ b/examples/us-treasury-auctions/README.md @@ -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 +``` + diff --git a/examples/us-treasury-auctions/TAuction.sol b/examples/us-treasury-auctions/TAuction.sol new file mode 100644 index 0000000..2af8c6f --- /dev/null +++ b/examples/us-treasury-auctions/TAuction.sol @@ -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; + + 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); + } 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? + 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); + } + +} diff --git a/examples/us-treasury-auctions/main.go b/examples/us-treasury-auctions/main.go new file mode 100644 index 0000000..5274ac3 --- /dev/null +++ b/examples/us-treasury-auctions/main.go @@ -0,0 +1,10 @@ +package main + +import ( + "github.com/flashbots/suapp-examples/framework" +) + +func main() { + fr := framework.New() + fr.DeployContract("TAuction.sol/TAuction.json") +}