Skip to content

Commit

Permalink
Merge pull request #224 from OffchainLabs/express-lane-auction-3
Browse files Browse the repository at this point in the history
Ability to set alternative transferor
  • Loading branch information
yahgwai authored Aug 15, 2024
2 parents e4d85e1 + ae50531 commit d4043bd
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 10 deletions.
2 changes: 2 additions & 0 deletions src/express-lane-auction/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ error ReserveBlackout();
error RoundTooOld(uint64 round, uint64 currentRound);
error RoundNotResolved(uint64 round);
error NotExpressLaneController(uint64 round, address controller, address sender);
error FixedTransferor(uint64 fixedUntilRound);
error NotTransferor(uint64 round, address expectedTransferor, address msgSender);
error InvalidNewRound(uint64 currentRound, uint64 newRound);
error InvalidNewStart(uint64 currentStart, uint64 newStart);
error RoundTooLong(uint64 roundDurationSeconds);
34 changes: 30 additions & 4 deletions src/express-lane-auction/ExpressLaneAuction.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,13 @@ import {
AccessControlEnumerableUpgradeable
} from "@openzeppelin/contracts-upgradeable/access/AccessControlEnumerableUpgradeable.sol";
import {DelegateCallAware} from "../libraries/DelegateCallAware.sol";
import {IExpressLaneAuction, Bid, InitArgs} from "./IExpressLaneAuction.sol";
import {IExpressLaneAuction, Bid, InitArgs, Transferor} from "./IExpressLaneAuction.sol";
import {ELCRound, LatestELCRoundsLib} from "./ELCRound.sol";
import {RoundTimingInfo, RoundTimingInfoLib} from "./RoundTimingInfo.sol";
import {
EIP712Upgradeable
} from "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol";

// CHRIS: TODO: add ability to set the transferrer of controller rights

/// @title ExpressLaneAuction
/// @notice The express lane allows a controller to submit undelayed transactions to the sequencer
/// The right to be the express lane controller are auctioned off in rounds, by an offchain auctioneer.
Expand Down Expand Up @@ -74,6 +72,9 @@ contract ExpressLaneAuction is
/// @inheritdoc IExpressLaneAuction
uint256 public beneficiaryBalance;

/// @inheritdoc IExpressLaneAuction
mapping(address => Transferor) public transferorOf;

/// @inheritdoc IExpressLaneAuction
function initialize(InitArgs memory args) public initializer onlyDelegated {
__AccessControl_init();
Expand Down Expand Up @@ -328,6 +329,7 @@ contract ExpressLaneAuction is
biddingForRound,
address(0),
firstPriceBid.expressLaneController,
address(0),
roundStart,
roundEnd
);
Expand Down Expand Up @@ -471,6 +473,22 @@ contract ExpressLaneAuction is
);
}

/// @inheritdoc IExpressLaneAuction
function setTransferor(Transferor calldata transferor) external {
// if a transferor has already been set, it may be fixed until a future round
Transferor storage currentTransferor = transferorOf[msg.sender];
if (
currentTransferor.addr != address(0) &&
currentTransferor.fixedUntilRound > roundTimingInfo.currentRound()
) {
revert FixedTransferor(currentTransferor.fixedUntilRound);
}

transferorOf[msg.sender] = transferor;

emit SetTransferor(msg.sender, transferor.addr, transferor.fixedUntilRound);
}

/// @inheritdoc IExpressLaneAuction
function transferExpressLaneController(uint64 round, address newExpressLaneController)
external
Expand All @@ -486,7 +504,14 @@ contract ExpressLaneAuction is
ELCRound storage resolvedRound = latestResolvedRounds.resolvedRound(round);

address resolvedELC = resolvedRound.expressLaneController;
if (resolvedELC != msg.sender) {
address transferor = transferorOf[resolvedELC].addr;
// can only be the transferor if one has been set
// otherwise we default to the express lane controller to do the transfer
if (transferor != address(0)) {
if (transferor != msg.sender) {
revert NotTransferor(round, transferor, msg.sender);
}
} else if (resolvedELC != msg.sender) {
revert NotExpressLaneController(round, resolvedELC, msg.sender);
}

Expand All @@ -497,6 +522,7 @@ contract ExpressLaneAuction is
round,
resolvedELC,
newExpressLaneController,
transferor != address(0) ? transferor : resolvedELC,
start < uint64(block.timestamp) ? uint64(block.timestamp) : start,
end
);
Expand Down
42 changes: 40 additions & 2 deletions src/express-lane-auction/IExpressLaneAuction.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ struct Bid {
bytes signature;
}

/// @notice Sets a transferor for an express lane controller
/// The transferor is an address that will have the right to transfer express lane controller rights
/// on behalf an express lane controller.
struct Transferor {
/// @notice The address of the transferor
address addr;
/// @notice The express lane controller can choose to fix the transferor until a future round number
/// This gives them ability to guarantee to other parties that they will not change transferor during an ongoing round
/// The express lane controller can ignore this feature by setting this value to 0.
uint64 fixedUntilRound;
}

/// @notice The arguments used to initialize an express lane auction
struct InitArgs {
/// @notice The address who can resolve auctions
Expand Down Expand Up @@ -100,16 +112,28 @@ interface IExpressLaneAuction is IAccessControlEnumerableUpgradeable, IERC165Upg
/// @param round The round which the express lane controller will control
/// @param previousExpressLaneController The previous express lane controller
/// @param newExpressLaneController The new express lane controller
/// @param transferor The address that transferored the controller rights. The transferor if set, otherwise the express lane controller
/// @param startTimestamp The timestamp at which the new express lane controller takes over
/// @param endTimestamp The timestamp at which the new express lane controller will cease to have control
event SetExpressLaneController(
uint64 round,
address previousExpressLaneController,
address newExpressLaneController,
address indexed previousExpressLaneController,
address indexed newExpressLaneController,
address indexed transferor,
uint64 startTimestamp,
uint64 endTimestamp
);

/// @notice A new transferor has been set for
/// @param expressLaneController The express lane controller that has a transferor
/// @param transferor The transferor chosen
/// @param fixedUntilRound The round until which this transferor is fixed for this controller
event SetTransferor(
address indexed expressLaneController,
address indexed transferor,
uint64 fixedUntilRound
);

/// @notice The minimum reserve price was set
/// @param oldPrice The previous minimum reserve price
/// @param newPrice The new minimum reserve price
Expand Down Expand Up @@ -181,6 +205,14 @@ interface IExpressLaneAuction is IAccessControlEnumerableUpgradeable, IERC165Upg
/// This is a gas optimisation to avoid making a transfer every time an auction is resolved
function beneficiaryBalance() external returns (uint256);

/// @notice Express lane controllers can optionally set a transferor address that has the rights
/// to transfer their controller rights. This function returns the transferor if one has been set
/// Returns the transferor for the supplied controller, and the round until which this
/// transferor is fixed if set.
function transferorOf(address expressLaneController)
external
returns (address addr, uint64 fixedUntil);

/// @notice Initialize the auction
/// @param args Initialization parameters
function initialize(InitArgs memory args) external;
Expand Down Expand Up @@ -333,6 +365,12 @@ interface IExpressLaneAuction is IAccessControlEnumerableUpgradeable, IERC165Upg
function resolveMultiBidAuction(Bid calldata firstPriceBid, Bid calldata secondPriceBid)
external;

/// @notice Sets a transferor for an express lane controller
/// The transferor is an address that will have the right to transfer express lane controller rights
/// on behalf an express lane controller.
/// @param transferor The transferor to set
function setTransferor(Transferor calldata transferor) external;

/// @notice Express lane controllers are allowed to transfer their express lane rights for the current or future
/// round to another address. They may use this for reselling their rights after purchasing them
/// Again, the priviledged accounts mentioned in resolve documentation are trusted not to try to receive rights via this message.
Expand Down
108 changes: 104 additions & 4 deletions test/foundry/ExpressLaneAuction.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,18 @@ contract ExpressLaneAuctionTest is Test {
event SetMinReservePrice(uint256 oldPrice, uint256 newPrice);
event SetExpressLaneController(
uint64 round,
address from,
address to,
address indexed from,
address indexed to,
address indexed transferor,
uint64 startTimestamp,
uint64 endTimestamp
);
event SetBeneficiary(address oldBeneficiary, address newBeneficiary);
event SetTransferor(
address indexed expressLaneController,
address indexed transferor,
uint64 fixedUntilRound
);
event SetRoundTimingInfo(
uint64 currentRound,
uint64 offsetTimestamp,
Expand Down Expand Up @@ -1086,6 +1092,7 @@ contract ExpressLaneAuctionTest is Test {
biddingForRound,
address(0),
bidders[1].elc,
address(0),
uint64(block.timestamp + auctionClosingSeconds),
uint64(block.timestamp + auctionClosingSeconds + roundDurationSeconds - 1)
);
Expand Down Expand Up @@ -1184,6 +1191,7 @@ contract ExpressLaneAuctionTest is Test {
biddingForRound,
address(0),
bidders[3].elc,
address(0),
uint64(block.timestamp + auctionClosingSeconds),
uint64(block.timestamp + auctionClosingSeconds + roundDurationSeconds - 1)
);
Expand Down Expand Up @@ -1328,6 +1336,7 @@ contract ExpressLaneAuctionTest is Test {
biddingForRound,
address(0),
bidders[1].elc,
address(0),
uint64(block.timestamp + auctionClosingSeconds),
uint64(block.timestamp + auctionClosingSeconds + roundDurationSeconds - 1)
);
Expand Down Expand Up @@ -1539,7 +1548,14 @@ contract ExpressLaneAuctionTest is Test {
(uint64 start, uint64 end) = rs.auction.roundTimestamps(testRound + 1);
vm.prank(bidders[1].elc);
vm.expectEmit(true, true, true, true);
emit SetExpressLaneController(testRound + 1, bidders[1].elc, bidders[0].elc, start, end);
emit SetExpressLaneController(
testRound + 1,
bidders[1].elc,
bidders[0].elc,
bidders[1].elc,
start,
end
);
rs.auction.transferExpressLaneController(testRound + 1, bidders[0].elc);

(, uint64 roundDurationSeconds, , ) = rs.auction.roundTimingInfo();
Expand All @@ -1558,6 +1574,7 @@ contract ExpressLaneAuctionTest is Test {
testRound + 1,
bidders[0].elc,
bidders[1].elc,
bidders[0].elc,
uint64(block.timestamp),
end
);
Expand Down Expand Up @@ -1587,6 +1604,7 @@ contract ExpressLaneAuctionTest is Test {
testRound + 1,
bidders[1].elc,
bidders[0].elc,
bidders[1].elc,
uint64(block.timestamp),
end
);
Expand All @@ -1608,8 +1626,90 @@ contract ExpressLaneAuctionTest is Test {
end = end + roundDuration;
vm.prank(bidders[3].elc);
vm.expectEmit(true, true, true, true);
emit SetExpressLaneController(testRound + 2, bidders[3].elc, bidders[2].elc, start, end);
emit SetExpressLaneController(
testRound + 2,
bidders[3].elc,
bidders[2].elc,
bidders[3].elc,
start,
end
);
rs.auction.transferExpressLaneController(testRound + 2, bidders[2].elc);

// set a transferor and have them transfer
vm.prank(bidders[2].elc);
rs.auction.setTransferor(Transferor(bidders[2].addr, 1000));

vm.prank(bidders[3].elc);
vm.expectRevert(
abi.encodeWithSelector(
testRound + 2,
NotTransferor.selector,
bidders[2].addr,
bidders[3].elc
)
);
rs.auction.transferExpressLaneController(testRound + 2, bidders[2].elc);

// change next now
vm.prank(bidders[2].addr);
vm.expectEmit(true, true, true, true);
emit SetExpressLaneController(
testRound + 2,
bidders[2].elc,
bidders[3].elc,
bidders[2].addr,
start,
end
);
rs.auction.transferExpressLaneController(testRound + 2, bidders[3].elc);
}

function testSetTransferor() public {
(, IExpressLaneAuction auction) = deploy();

address elc = vm.addr(1559);
address transferor = vm.addr(1560);
address transferor2 = vm.addr(1561);
uint64 fixedUntilRound = 137;
address actualTransferor;
uint64 actualFixedUntil;
(actualTransferor, actualFixedUntil) = auction.transferorOf(elc);
assertEq(actualTransferor, address(0));
assertEq(actualFixedUntil, 0);

vm.prank(elc);
vm.expectEmit(true, true, true, true);
emit SetTransferor(elc, transferor, 0);
auction.setTransferor(Transferor({addr: transferor, fixedUntilRound: 0}));
(actualTransferor, actualFixedUntil) = auction.transferorOf(elc);
assertEq(actualTransferor, transferor);
assertEq(actualFixedUntil, 0);

vm.prank(elc);
vm.expectEmit(true, true, true, true);
emit SetTransferor(elc, transferor2, fixedUntilRound);
auction.setTransferor(Transferor({addr: transferor2, fixedUntilRound: fixedUntilRound}));
(actualTransferor, actualFixedUntil) = auction.transferorOf(elc);
assertEq(actualTransferor, transferor2);
assertEq(actualFixedUntil, fixedUntilRound);

vm.prank(elc);
vm.expectRevert(abi.encodeWithSelector(FixedTransferor.selector, fixedUntilRound));
auction.setTransferor(Transferor({addr: transferor, fixedUntilRound: fixedUntilRound + 1}));

while (auction.currentRound() < fixedUntilRound) {
vm.warp(block.timestamp + roundDuration);
}

assertEq(auction.currentRound(), fixedUntilRound);
vm.prank(elc);
vm.expectEmit(true, true, true, true);
emit SetTransferor(elc, transferor, fixedUntilRound + 1);
auction.setTransferor(Transferor({addr: transferor, fixedUntilRound: fixedUntilRound + 1}));
(actualTransferor, actualFixedUntil) = auction.transferorOf(elc);
assertEq(actualTransferor, transferor);
assertEq(actualFixedUntil, fixedUntilRound + 1);
}

function testSetBeneficiary() public {
Expand Down

0 comments on commit d4043bd

Please sign in to comment.