Alex Otsu

Posted on Mar 28, 2022Read on Mirror.xyz

The Stargate Finance Auction Debacle - Alex Otsu - Medium

Full text, including code used to analyze transactions, available on GitHub

19.537 ETH, or roughly $55,000 (at the time of the transactions). That is how much was spent in an attempt to be at the front of the line to purchase STG, Stargate Finance’s cross-chain ecosystem token. This piece will break down the three successful transactions that constituted the entire sale, as well as the activity from before and after. After reading it, you should walk away with an understanding for the strategy used by the frontrunner and some of the risks involved.

Background

The first public Stargate Finance token sale ended somewhat infamously with two addresses purchasing the entire $25 million reserve over three transactions, leaving nothing left for the thousands of others who were expecting to take part. It was later confirmed by Alameda Research CEO Sam Trabucco that the trading giant was the whale behind both accounts.

While Alameda taking a 100% position in your project is about as strong a vote of confidence as there is, it prompted the Stargate team to publicly reflect on the token sale design and eventually announce a second tranche for other users to purchase. This time, purchases by eligible accounts will be capped to 18,657 STG.

Contracts

There were two main contracts involved in the sale: StargateToken and Auction. Both are ERC-20’s, with the former minting STG tokens and the latter minting aSTG. In this transaction,, Auction had 100,000,000 STG transferred to it to fund redemptions after the lockup period.

Auction has two key functions: enter() and redeem(). Just by looking at the names, it should be apparent that enter() was the main function that people would be interacting with. But it can be confirmed in hindsight by looking at the auction function history on Etherscan.

The relevant parts of the code are below:

uint astgDecimals // 6
uint public auctionStartTime; // 1647532800, or March 17, 2022 at 4pm UTC
uint public auctionCap; // 25000000000000, or 25,000,000 tokens at astgDecimals()

function enter(uint _amount) external {
    require(block.timestamp >= auctionStartTime, "auction not started");
    require(block.timestamp < auctionEndTime && auctionedAmount < auctionCap, "auction finished");
    require(_amount > 0, "amount too small");

    uint amount = _amount;
    uint quota = auctionCap - auctionedAmount;
    if(amount > quota) {
        amount = quota;
    }

    stableCoin.safeTransferFrom(msg.sender, stargateTreasury, amount);
    auctionedAmount += amount;
    _mint(msg.sender, amount);

    emit Auctioned(msg.sender, amount);
}

The require statements only check that the auction has started, that the auction cap has not been reached, and that the amount of stableCoin the user is transferring is greater than zero. Those statements provide clues for to how to frontrun all other purchasers: first, ensure the transaction is in the first block greater or equal to auctionStartTime, and then ensure the transactions do not attempt to purchase more than the remaining auction amount.

The astute reader will note that while there were 100,000,000 STG funding the contract, only 25,000,000 aSTG were issued. Essentially, each aSTG is redeemable for four STG when the vesting period finishes, as cleverly accomplished in this line in the redeem() function:

uint stargateAmount = stgAuctionAmount * newSharesToRedeem / totalSupply(); // stgAuctionAmount == 100m, newSharesToRedeem == vested, totalSupply() == aSTG totalSupply, or 25,000,000

Transactions — Before auctionStart

After having identified our contract of interest, we can analyze the transactions themselves. The first thing to notice is that there were 21 transactions that attempted to call the Enter function before the block with the correct timestamp had been mined. This was likely in case the block ended up taking a long time to mine. Note that there were only five addresses submitting transactions at this point. The two addresses that ended up winning had already spent 2.541666 and 2.179671 ETH, respectively, in gas fees at this point. They, in addition to a third address, appear to be spamming the same transaction six times each.

Snippet of the transactions made before the auction started. Fork the Jupyter Notebook to play with the values yourself.

Transactions — First Block After auctionStart

With previous block finishing at 15:59:35, the addresses did not want to take chances and started submitting very expensive transactions. Their strategy paid off, as the block 14404984 was mined at 16:00:26. The gas prices paid were 15k Gwei, 17k Gwei, and 17k Gwei. This time, seven addresses were in the mix. Despite winning the auction, the same addresses still had pending transactions that were mined despite returning an error.

Overall, the winning transactions cost a 5.744 Ether to send.

The total amount of ETH spent trying to secure the auction was 8.788.

Snippet of transactions made during the block where block.timestamp fulfilled the auctionStart time

The rest of the transactions from the block are below. It appears that they scaled the Gwei limit down after the first three transactions. Some other actors tried a similar strategy of entering multiple transactions, but did not use high enough gas.

All the addresses and what they spent trying to be the first in line for $STG.

Summary

In summary, it was a whale’s auction, with the winners spending nearly 6 ETH to secure their position at the front of the line. In total, 19.57 ETH was given to miners in an attempt to purchase STG. Future auctions of the same design can be exploited in the same way, if you have the cash.