Why can't a module own resources, despite being within an account?

Hello again :slightly_smiling_face:
I wonder what is the rationale behind letting only external (“human operated”) accounts own resources? Why can’t smart contracts own resources? (I understand that technically resource moving works only from the signer [“human”], but I am interested to know the reason behind this design choice)

For example:
How an NFT auctioning contract would work? (using an external NFT contract, such as non_fungible_token.move)

1 Like

Good question! Rather than answering directly, let me summarize a few key points and then answer:

  • Move modules are code and Move resources are data
  • Both modules and code are stored under account addresses. So it doesn’t really make sense to say that a module “owns” a resource; they are just different types of entities.
  • The “human operator” of an account at address A may or may not have the authority to mutate or remove a resource T stored under A. The module that defines T gets to set this policy.

I think an example illustrates this most clearly. Here is a rough cut at an auction contract that could be used to auction off any resource (including NFTs). There are lots of small problems with this code, but hopefully it conveys the general idea:

  • The actors in this contract are an auctioneer address, an arbitrary number of bidder addresses, and a beneficiary address
  • The auctioneer address stores the Auction resource with the goods to be be auctioned and the bidding information, but doesn’t “own” the good in any sense (i.e., the auctioneer cannot modify the resource or choose to transfer the goods). The auctioneer also does not need to send any transactions after creating the Auction resource.
  • ToSell is a generic type that could be bound to anything, including a NFT defined in a different module
module Auction {
    use 0x1::Errors;
    use 0x1::Libra::Libra;
    use 0x1::LibraAccount;
    use 0x1::Signer;

    // Published under the auctioneer's account
    // ToSell is the type of the value to be auctioned.
    // It can be defined in another module (e.g., NonFungibleToken.move)
    // Currency is the type of coin the beneficiary would like to receive
    resource struct Auction<ToSell,Currency> {
        to_sell: ToSell, // the value to be aucitioned
        beneficiary: address, // address that will receive proceeds from the auction
        max_bid: u64,
        // ... fields for min_bid, the end time of the auction, etc.
    }

    // A bid resource granted to someone who bids `bid` coins for the
    // resource ToSell being auctioned at auction_addr
    resource struct Bid<ToSell,Currency> {
        bid: Libra<Currency>,
        auction_addr: address
    }

    const EBID_TOO_SMALL: u64 = 0;

    public fun create<ToSell,Currency>(auctioneer: &signer, beneficiary: address, to_sell: ToSell) {
        move_to<Auction<ToSell,Currency>>(auctioneer, Auction<ToSell> { to_sell, beneficiary, max_bid: 0 })
    }

    public fun bid<ToSell,Currency>(
        bidder: &signer, auction_addr: address, bid: Libra<Currency>
    ) acquires Auction {
        let bid_amount = Libra::value(&bid);
        // retrieve the Auction resource at addr and update the max bid
        let old_max_bid = borrow_global_mut<Auction<ToSell,Currency>>(auction_addr).max_bid;
        if bid_amount > old_max_bid {
            // new max bid; update auction resource and publish the bid under the bidder's account
            *old_max_bid = bid_amount;
            move_to(bidder, Bid<ToSell,Currency> { bid, auction_addr });
        } else {
            // this bid is not going to win the auction; abort
            abort(Errors::invalid_state(EBID_TOO_SMALL))
        }
    }

    // if this is the winning bid, give the funds to the auctioneer and the ToSell to the bidder
    public fun redeem_winning_bid<ToSell,Currency>(bidder: &account): ToSell acquires Auction, Bid {
        let Bid { bid, auction_addr } = move_from<Bid<ToSell,Currency>>(Signer::address_of(bidder));
        let Auction { to_sell, beneficiary, max_bid } = move_from<Auction<ToSell,Currency>>(auction_addr);
        // ... we would want to check that the Auction is over here. we'll assume it for simplicity
        // make sure this is actually the winning bid!
        assert(Libra::value(&bid) == max_bid, Errors::invalid_state(EBID_TOO_SMALL));
        // give the beneficiary the funds
        LibraAccount::deposit(bid, auction_addr);

        to_sell
    }

    // ... procedures to refund non-winning bids, re-bid, etc.
}
2 Likes

Thank you for your thorough answer (as usual :slightly_smiling_face:)

Now I understand the design paradigm better (as implemented in Offer.move), and I think it’s cool that the original resource “travels” alongside the new resource (in your example Auction{}) .

Hey, Sam!

I know it’s a bit off-topic but can you elaborate a bit on Auction contract? In this sample every bidder stores the bid on his account, and link to highest bid is stored in Auction resource (is that right?). Why not store the bid inside auction contract and release assets on every higher bid to previous max bidder instead of spawning resources that need to be destroyed when auction ends?

Also. How, from user perspective and UX, would people free their funds? Would it require a lot of off-chain tracking? People might miss the end of auction and the actual end of auction for smaller bids comes with the next bidder! Or is there a solution for cases like this? I am asking this because I’m stuck with similar case and will be glad to receive any piece of advice. :alien:

That’s a lot of bid!

Why not store the bid inside auction contract and release assets on every higher bid to previous max bidder instead of spawning resources that need to be destroyed when auction ends?

Good point, this would be a better design! I copied my approach to the Auction from a crowdfunding protype I had sitting around, but I think your idea makes more sense.

Also. How, from user perspective and UX, would people free their funds? Would it require a lot of off-chain tracking?

An event handle associated with the Auction resource that each bidder can watch + events for each new bid and for the end of the auction would probably help with this. I think determining when the auction is over will always require some off-chain tracking–you’ll need to look at the time or at an event stream (depending on how you define the ends).

People might miss the end of auction and the actual end of auction for smaller bids comes with the next bidder!

I didn’t understand this part–could you explain in more detail?

What I meant is that everyone in your scenario would have to wait for the auction end event (?) to destroy their bid resource. And another point of view is that every bidder’s activity suddenly turns to nothing when higher bid is placed. So bidder should destroy his bid not after the auction ends but after higher bid has been made.

Anyway it does not matter anymore. :laughing: Your example clearly shows the use of and ownership over resources. But I’m glad you agreed with my solution (it will see the daylight on testnet pretty soon).

If I may… I think I asked you this but. Is there a nice solution for viewing resources only knowing account? Or maybe if we add some knowledge about module and specific struct we want to see? Obviously I’m expecting something like Ethereum’s “public view functions”. :smirk:

1 Like

Thanks for clarifying! Your explanation makes sense and I agree that those are problems with my proposed approach that are fixed by your better solution.

If I may… I think I asked you this but. Is there a nice solution for viewing resources only knowing account? Or maybe if we add some knowledge about module and specific struct we want to see? Obviously I’m expecting something like Ethereum’s “public view functions”. :smirk:

We have just started working on this! The move view command of the new Move CLI is intended to serve this purpose. It currently supports viewing individual resources and modules, but we are planning to extend it to viewing events and accounts.

1 Like

Damn! I didn’t know that you have such a nice and beautiful tool! It’s a pity that we haven’t joined forces on this. :confused:

Not documented though does the same and a bit more customisable (has a switch to dfinance/libra dialects and any number of folders with modules). And prints out gas usage + all debug info.