Skip to content

Commit

Permalink
Merge pull request #85 from primevprotocol/ckartik/update-bid-type
Browse files Browse the repository at this point in the history
Implements Pre-Confirmations Decay Handling for Smart Contracts
  • Loading branch information
ckartik authored Mar 10, 2024
2 parents 511b6f5 + 335453e commit d2fa528
Show file tree
Hide file tree
Showing 14 changed files with 439 additions and 241 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ jobs:
uses: foundry-rs/foundry-toolchain@v1

- name: Run tests
run: forge test -vvv
run: forge test -vvv --via-ir

- name: Run snapshot
run: forge snapshot
run: forge snapshot --via-ir

- name: Run coverage
run: forge coverage
run: forge coverage --ir-minimum

build:
name: Hardhat Checks and Scripts
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ WORKDIR /app
COPY . .

# Compile contracts using solidity compiler version 0.8.23
RUN forge build --use 0.8.23
RUN forge build --use 0.8.23 --via-ir

# Set environment variables for RPC URL and private key
# These should be passed during the Docker build process
Expand Down
20 changes: 12 additions & 8 deletions contracts/BidderRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -163,28 +163,32 @@ contract BidderRegistry is IBidderRegistry, Ownable, ReentrancyGuard {
*/
function retrieveFunds(
bytes32 commitmentDigest,
address payable provider
address payable provider,
uint256 residualBidPercentAfterDecay
) external nonReentrant onlyPreConfirmationEngine {

BidState memory bidState = BidPayment[commitmentDigest];
require(bidState.state == State.PreConfirmed, "The bid was not preconfirmed");
uint256 amt = bidState.bidAmt;
uint256 feeAmt = (amt * uint256(feePercent) * PRECISION) / PERCENT;
uint256 amtMinusFee = amt - feeAmt;
uint256 decayedAmt = ( bidState.bidAmt * residualBidPercentAfterDecay * PRECISION) / PERCENT;

uint256 feeAmt = (decayedAmt * uint256(feePercent) * PRECISION) / PERCENT;
uint256 amtMinusFeeAndDecay = decayedAmt - feeAmt;

if (feeRecipient != address(0)) {
feeRecipientAmount += feeAmt;
} else {
protocolFeeAmount += feeAmt;
}

providerAmount[provider] += amtMinusFee;
providerAmount[provider] += amtMinusFeeAndDecay;

// Ensures the bidder gets back the bid amount - decayed reward given to provider and protocol
bidderPrepaidBalances[bidState.bidder] += bidState.bidAmt - decayedAmt;

// TODO(@ckartik): Ensure we throughly test this flow
BidPayment[commitmentDigest].state = State.Withdrawn;
BidPayment[commitmentDigest].bidAmt = 0;

emit FundsRetrieved(commitmentDigest, amt);
emit FundsRetrieved(commitmentDigest, decayedAmt);
}

/**
Expand All @@ -201,7 +205,7 @@ contract BidderRegistry is IBidderRegistry, Ownable, ReentrancyGuard {

BidPayment[bidID].state = State.Withdrawn;
BidPayment[bidID].bidAmt = 0;

emit FundsRetrieved(bidID, amt);
}

Expand Down
13 changes: 7 additions & 6 deletions contracts/Oracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -95,14 +95,15 @@ contract Oracle is Ownable {
bytes32 commitmentIndex,
uint256 blockNumber,
string calldata blockBuilderName,
bool isSlash
bool isSlash,
uint256 residualBidPercentAfterDecay
) external onlyOwner {
// Check graffiti against registered builder IDs
address builder = blockBuilderNameToAddress[blockBuilderName];

require(residualBidPercentAfterDecay <= 100, "Residual bid after decay cannot be greater than 100 percent");
IPreConfCommitmentStore.PreConfCommitment memory commitment = preConfContract.getCommitment(commitmentIndex);
if (commitment.commiter == builder && commitment.blockNumber == blockNumber) {
processCommitment(commitmentIndex, isSlash);
processCommitment(commitmentIndex, isSlash, residualBidPercentAfterDecay);
}

}
Expand Down Expand Up @@ -137,11 +138,11 @@ contract Oracle is Ownable {
* @param commitmentIndex The id of the commitment to be processed.
* @param isSlash Determines if the commitment should be slashed or rewarded.
*/
function processCommitment(bytes32 commitmentIndex, bool isSlash) private {
function processCommitment(bytes32 commitmentIndex, bool isSlash, uint256 residualBidPercentAfterDecay) private {
if (isSlash) {
preConfContract.initiateSlash(commitmentIndex);
preConfContract.initiateSlash(commitmentIndex, residualBidPercentAfterDecay);
} else {
preConfContract.initateReward(commitmentIndex);
preConfContract.initiateReward(commitmentIndex, residualBidPercentAfterDecay);
}
// Emit an event that a commitment has been processed
emit CommitmentProcessed(commitmentIndex, isSlash);
Expand Down
57 changes: 44 additions & 13 deletions contracts/PreConfirmations.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {IProviderRegistry} from "./interfaces/IProviderRegistry.sol";
import {IBidderRegistry} from "./interfaces/IBidderRegistry.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";

import "forge-std/console.sol";

/**
* @title PreConfCommitmentStore - A contract for managing preconfirmation commitments and bids.
* @notice This contract allows bidders to make precommitments and bids and provides a mechanism for the oracle to verify and process them.
Expand All @@ -18,12 +20,12 @@ contract PreConfCommitmentStore is Ownable {
/// @dev EIP-712 Type Hash for preconfirmation commitment
bytes32 public constant EIP712_COMMITMENT_TYPEHASH =
keccak256(
"PreConfCommitment(string txnHash,uint64 bid,uint64 blockNumber,string bidHash,string signature)"
"PreConfCommitment(string txnHash,uint64 bid,uint64 blockNumber,uint64 decayStartTimeStamp,uint64 decayEndTimeStamp,string bidHash,string signature)"
);

/// @dev EIP-712 Type Hash for preconfirmation bid
bytes32 public constant EIP712_MESSAGE_TYPEHASH =
keccak256("PreConfBid(string txnHash,uint64 bid,uint64 blockNumber)");
bytes32 public constant EIP712_BID_TYPEHASH =
keccak256("PreConfBid(string txnHash,uint64 bid,uint64 blockNumber,uint64 decayStartTimeStamp,uint64 decayEndTimeStamp)");

/// @dev commitment counter
uint256 public commitmentCount;
Expand Down Expand Up @@ -67,10 +69,13 @@ contract PreConfCommitmentStore is Ownable {
uint64 bid;
uint64 blockNumber;
bytes32 bidHash;
uint64 decayStartTimeStamp;
uint64 decayEndTimeStamp;
string txnHash;
bytes32 commitmentHash;
bytes bidSignature;
bytes commitmentSignature;
uint256 blockCommitedAt;
}

/// @dev Event to log successful verifications
Expand Down Expand Up @@ -149,17 +154,21 @@ contract PreConfCommitmentStore is Ownable {
function getBidHash(
string memory _txnHash,
uint64 _bid,
uint64 _blockNumber
uint64 _blockNumber,
uint64 _decayStartTimeStamp,
uint64 _decayEndTimeStamp
) public view returns (bytes32) {
return
ECDSA.toTypedDataHash(
DOMAIN_SEPARATOR_BID,
keccak256(
abi.encode(
EIP712_MESSAGE_TYPEHASH,
EIP712_BID_TYPEHASH,
keccak256(abi.encodePacked(_txnHash)),
_bid,
_blockNumber
_blockNumber,
_decayStartTimeStamp,
_decayEndTimeStamp
)
)
);
Expand All @@ -177,6 +186,8 @@ contract PreConfCommitmentStore is Ownable {
string memory _txnHash,
uint64 _bid,
uint64 _blockNumber,
uint64 _decayStartTimeStamp,
uint64 _decayEndTimeStamp,
bytes32 _bidHash,
string memory _bidSignature
) public view returns (bytes32) {
Expand All @@ -189,6 +200,8 @@ contract PreConfCommitmentStore is Ownable {
keccak256(abi.encodePacked(_txnHash)),
_bid,
_blockNumber,
_decayStartTimeStamp,
_decayEndTimeStamp,
keccak256(
abi.encodePacked(_bytes32ToHexString(_bidHash))
),
Expand All @@ -212,14 +225,16 @@ contract PreConfCommitmentStore is Ownable {
function verifyBid(
uint64 bid,
uint64 blockNumber,
uint64 decayStartTimeStamp,
uint64 decayEndTimeStamp,
string memory txnHash,
bytes calldata bidSignature
)
public
view
returns (bytes32 messageDigest, address recoveredAddress, uint256 stake)
{
messageDigest = getBidHash(txnHash, bid, blockNumber);
messageDigest = getBidHash(txnHash, bid, blockNumber, decayStartTimeStamp, decayEndTimeStamp);
recoveredAddress = messageDigest.recover(bidSignature);
stake = bidderRegistry.getAllowance(recoveredAddress);
require(stake > (10 * bid), "Invalid bid");
Expand All @@ -240,6 +255,8 @@ contract PreConfCommitmentStore is Ownable {
string memory txnHash,
uint64 bid,
uint64 blockNumber,
uint64 decayStartTimeStamp,
uint64 decayEndTimeStamp,
bytes32 bidHash,
bytes memory bidSignature,
bytes memory commitmentSignature
Expand All @@ -252,6 +269,8 @@ contract PreConfCommitmentStore is Ownable {
txnHash,
bid,
blockNumber,
decayStartTimeStamp,
decayEndTimeStamp,
bidHash,
_bytesToHexString(bidSignature)
);
Expand Down Expand Up @@ -283,12 +302,16 @@ contract PreConfCommitmentStore is Ownable {
uint64 bid,
uint64 blockNumber,
string memory txnHash,
uint64 decayStartTimeStamp,
uint64 decayEndTimeStamp,
bytes calldata bidSignature,
bytes memory commitmentSignature
) public returns (bytes32 commitmentIndex) {
(bytes32 bHash, address bidderAddress, uint256 stake) = verifyBid(
bid,
blockNumber,
decayStartTimeStamp,
decayEndTimeStamp,
txnHash,
bidSignature
);
Expand All @@ -298,25 +321,31 @@ contract PreConfCommitmentStore is Ownable {
txnHash,
bid,
blockNumber,
decayStartTimeStamp,
decayEndTimeStamp,
bHash,
_bytesToHexString(bidSignature)
);

address commiterAddress = commitmentDigest.recover(commitmentSignature);

require(stake > (10 * bid), "Stake too low");

require(decayStartTimeStamp < decayEndTimeStamp, "Invalid decay time");

PreConfCommitment memory newCommitment = PreConfCommitment(
false,
bidderAddress,
commiterAddress,
bid,
blockNumber,
bHash,
decayStartTimeStamp,
decayEndTimeStamp,
txnHash,
commitmentDigest,
bidSignature,
commitmentSignature
commitmentSignature,
block.number
);

commitmentIndex = getCommitmentIndex(newCommitment);
Expand Down Expand Up @@ -391,7 +420,7 @@ contract PreConfCommitmentStore is Ownable {
* @dev Initiate a slash for a commitment.
* @param commitmentIndex The hash of the commitment to be slashed.
*/
function initiateSlash(bytes32 commitmentIndex) public onlyOracle {
function initiateSlash(bytes32 commitmentIndex, uint256 residualBidPercentAfterDecay) public onlyOracle {
PreConfCommitment memory commitment = commitments[commitmentIndex];
require(
!commitments[commitmentIndex].commitmentUsed,
Expand All @@ -405,7 +434,8 @@ contract PreConfCommitmentStore is Ownable {
providerRegistry.slash(
commitment.bid,
commitment.commiter,
payable(commitment.bidder)
payable(commitment.bidder),
residualBidPercentAfterDecay
);

bidderRegistry.unlockFunds(commitment.commitmentHash);
Expand All @@ -423,7 +453,7 @@ contract PreConfCommitmentStore is Ownable {
* @dev Initiate a reward for a commitment.
* @param commitmentIndex The hash of the commitment to be rewarded.
*/
function initateReward(bytes32 commitmentIndex) public onlyOracle {
function initiateReward(bytes32 commitmentIndex, uint256 residualBidPercentAfterDecay) public onlyOracle {
PreConfCommitment memory commitment = commitments[commitmentIndex];
require(
!commitments[commitmentIndex].commitmentUsed,
Expand All @@ -436,7 +466,8 @@ contract PreConfCommitmentStore is Ownable {

bidderRegistry.retrieveFunds(
commitment.commitmentHash,
payable(commitment.commiter)
payable(commitment.commiter),
residualBidPercentAfterDecay
);
}

Expand Down
15 changes: 9 additions & 6 deletions contracts/ProviderRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -142,21 +142,24 @@ contract ProviderRegistry is IProviderRegistry, Ownable, ReentrancyGuard {

/**
* @dev Slash funds from the provider and send the slashed amount to the bidder.
* @dev reenterancy not necessary but still putting here for precaution
* @dev reenterancy not necessary but still putting here for precaution.
* @dev Note we slash the funds irrespective of decay, this is to prevent timing games.
* @param amt The amount to slash from the provider's stake.
* @param provider The address of the provider.
* @param bidder The address to transfer the slashed funds to.
*/
function slash(
uint256 amt,
address provider,
address payable bidder
address payable bidder,
uint256 residualBidPercentAfterDecay
) external nonReentrant onlyPreConfirmationEngine {
require(providerStakes[provider] >= amt, "Insufficient funds to slash");
providerStakes[provider] -= amt;
uint256 residualAmt = (amt * residualBidPercentAfterDecay * PRECISION) / PERCENT;
require(providerStakes[provider] >= residualAmt, "Insufficient funds to slash");
providerStakes[provider] -= residualAmt;

uint256 feeAmt = (amt * uint256(feePercent) * PRECISION) / PERCENT;
uint256 amtMinusFee = amt - feeAmt;
uint256 feeAmt = (residualAmt * uint256(feePercent) * PRECISION) / PERCENT;
uint256 amtMinusFee = residualAmt - feeAmt;

if (feeRecipient != address(0)) {
feeRecipientAmount += feeAmt;
Expand Down
3 changes: 2 additions & 1 deletion contracts/interfaces/IBidderRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ interface IBidderRegistry {

function retrieveFunds(
bytes32 commitmentDigest,
address payable provider
address payable provider,
uint256 residualBidPercentAfterDecay
) external;

function unlockFunds(bytes32 bidID) external;
Expand Down
8 changes: 6 additions & 2 deletions contracts/interfaces/IPreConfirmations.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,17 @@ interface IPreConfCommitmentStore {
uint64 bid;
uint64 blockNumber;
bytes32 bidHash;
uint64 decayStartTimeStamp;
uint64 decayEndTimeStamp;
string txnHash;
bytes32 commitmentHash;
bytes bidSignature;
bytes commitmentSignature;
uint256 blockCommitedAt;
}



event SignatureVerified(
address indexed signer,
string txnHash,
Expand Down Expand Up @@ -71,9 +75,9 @@ interface IPreConfCommitmentStore {

function getCommitment(bytes32 commitmentIndex) external view returns (PreConfCommitment memory);

function initiateSlash(bytes32 commitmentIndex) external;
function initiateSlash(bytes32 commitmentIndex, uint256 residualDecayedBid) external;

function initateReward(bytes32 commitmentIndex) external;
function initiateReward(bytes32 commitmentIndex, uint256 residualDecayedBid) external;

function unlockBidFunds(bytes32 commitmentDigest) external;

Expand Down
3 changes: 2 additions & 1 deletion contracts/interfaces/IProviderRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ interface IProviderRegistry {
function slash(
uint256 amt,
address provider,
address payable bidder
address payable bidder,
uint256 residualBidPercentAfterDecay
) external;
}
Loading

0 comments on commit d2fa528

Please sign in to comment.