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

Ability to set alternative transferor #224

Merged
merged 10 commits into from
Aug 15, 2024
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,3 +20,5 @@ error ReserveBlackout();
error RoundTooOld(uint256 round, uint256 currentRound);
error RoundNotResolved(uint256 round);
error NotExpressLaneController(uint64 round, address controller, address sender);
error FixedTransferor(uint64 fixedUntilRound);
error NotTransferor(address expectedTransferor, address msgSender);
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,7 +9,7 @@ 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";

Expand Down Expand Up @@ -41,8 +41,6 @@ import {RoundTimingInfo, RoundTimingInfoLib} from "./RoundTimingInfo.sol";
// * reducing the round time does have an effect on finalize - add this later
// * check finalization times with round time update

// 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 @@ -98,6 +96,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 {
if (address(args._biddingToken) == address(0)) {
Expand Down Expand Up @@ -292,6 +293,7 @@ contract ExpressLaneAuction is
biddingForRound,
address(0),
firstPriceBid.expressLaneController,
address(0),
roundStart,
roundEnd
);
Expand Down Expand Up @@ -431,6 +433,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()
Copy link
Member

Choose a reason for hiding this comment

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

maybe its better to fix to a timestamp (and rounding up 1 round) instead? otherwise changing round time might make the fix duration change

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think that's fine - round duration changes might also warrant a change of fix time. Also changing to timestamp might mean that you cant guarantee that the next round will have a fixed transferor

) {
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 @@ -446,7 +464,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(transferor, msg.sender);
yahgwai marked this conversation as resolved.
Show resolved Hide resolved
}
} else if (resolvedELC != msg.sender) {
revert NotExpressLaneController(round, resolvedELC, msg.sender);
}

Expand All @@ -457,6 +482,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 @@ -98,16 +110,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 @@ -165,6 +189,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 @@ -296,6 +328,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
/// @param round The round to transfer rights for
Expand Down
105 changes: 100 additions & 5 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
);

uint64 roundDuration = 60; // 1 min

Expand Down Expand Up @@ -625,7 +631,7 @@ contract ExpressLaneAuctionTest is Test {
}

function testGetBidBytes() public {
(MockERC20 erc20, IExpressLaneAuction auction) = deployAndDeposit();
(, IExpressLaneAuction auction) = deployAndDeposit();
uint64 biddingForRound = auction.currentRound() + 1;
bytes memory b0 = auction.getBidBytes(
biddingForRound,
Expand Down Expand Up @@ -1175,6 +1181,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 @@ -1286,6 +1293,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 @@ -1430,6 +1438,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 @@ -1641,7 +1650,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 @@ -1660,6 +1676,7 @@ contract ExpressLaneAuctionTest is Test {
testRound + 1,
bidders[0].elc,
bidders[1].elc,
bidders[0].elc,
uint64(block.timestamp),
end
);
Expand Down Expand Up @@ -1695,6 +1712,7 @@ contract ExpressLaneAuctionTest is Test {
testRound + 1,
bidders[1].elc,
bidders[0].elc,
bidders[1].elc,
uint64(block.timestamp),
end
);
Expand All @@ -1716,8 +1734,85 @@ 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(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
Loading