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.
}