Assessment reports>Laminar Markets>High findings>Potential frontrunning in orderbook create
Category: Coding Mistakes

Potential frontrunning in orderbook create

High Severity
High Impact
High Likelihood

Description

The book::create_orderbook function calls account::create_resource_account. The latter takes a signer and a seed to calculate an address and then creates an account at that address. This behavior is shown in the following snippet:

let seed_guid = account::create_guid(account);
let seed = bcs::to_bytes<GUID>(&seed_guid);
let (book_signer, book_signer_cap) = account::create_resource_account(account, seed);

If the address of the signer and the seed are known, the address that account::create_resource_account will use can be determined. Therefore, an attacker can front-run book::create_orderbook by creating an account at the right address, causing book::create_orderbook to revert.

The seed and address are trivial to determine; an address is public information and the seed is simply the guid_creation_num member of the Account struct. Therefore, the seed can be read from the blockchain.

Impact

Affected users will not be allowed to create orderbooks, which will result in them not being able to use the market.

The following unit test demonstrates how an attacker could front-run book::create_orderbook:

#[test(account = @dex)]
#[expected_failure]
fun create_fake_orderbook(account: &signer) {
    create_fake_coins(account);

    let victim_addr = signer::address_of(account);
    let guid_creation_num = account::get_guid_next_creation_num(victim_addr);

    let seed_id = guid::create_id(victim_addr, guid_creation_num);
    let seed_guid = GUID {
                            id: seed_id
                         };
    let seed = bcs::to_bytes<GUID>(&seed_guid);

    let new_addr = account::create_resource_address(&victim_addr, seed);
    aptos_account::create_account(new_addr);

    // Should fail
    book::create_orderbook<FakeBaseCoin, FakeQuoteCoin>(account, 3, 3, 1000);
}

We have provided the full PoC to Laminar for reproduction and verification.

Recommendations

Consider using a nondeterministic seed to create the resource account.

Remediation

Commit 925e8a4 in aptos-core, introduced by Aptos during the audit prevents the front running of resource accounts via an override if an account exists at the resource_addr.

let resource = if (exists_at(resource_addr)) {
            let account = borrow_global<Account>(resource_addr);
            assert!(
                option::is_none(&account.signer_capability_offer.for),
                error::already_exists(ERESOURCE_ACCCOUNT_EXISTS),
            );
            assert!(
                account.sequence_number == 0,
                error::invalid_state(EACCOUNT_ALREADY_USED),
            );
            create_signer(resource_addr)
Zellic © 2024Back to top ↑