# Module `0x1::offer`
Provides a way to transfer structs from one account to another in two transactions.
Unlike many languages, Move cannot move data from one account to another with
single-signer transactions. As of this writing, ordinary transactions can only have
a single signer, and Move code can only store to an address (via move_to
) if it
can supply a reference to a signer for the destination address (there are special case
exceptions in Genesis and DiemAccount where there can temporarily be multiple signers).
Offer solves this problem by providing an Offer
resource. To move a struct T
from
account A to B, account A first publishes an Offer<T>
resource at address_of(A)
,
using the offer::create
function.
Then account B, in a separate transaction, can move the struct T
from the Offer
at
A's address to the desired destination. B accesses the resource using the redeem
function,
which aborts unless the for
field is B's address (preventing other addresses from
accessing the T
that is intended only for B). A can also redeem the T
value if B hasn't
redeemed it.
- [Resource `Offer`](#0x1_offer_Offer)
- [Constants](#@Constants_0)
- [Function `create`](#0x1_offer_create)
- [Function `redeem`](#0x1_offer_redeem)
- [Function `exists_at`](#0x1_offer_exists_at)
- [Function `address_of`](#0x1_offer_address_of)
- [Module Specification](#@Module_Specification_1)
- [Access Control](#@Access_Control_2)
- [Creation of Offers](#@Creation_of_Offers_3)
- [Removal of Offers](#@Removal_of_Offers_4)
- [Helper Functions](#@Helper_Functions_5)
use 0x1::error;
use 0x1::signer;
## Resource `Offer`
A wrapper around value offered
that can be claimed by the address stored in for
.
struct Offer<Offered> has key
Fields
-
offered: Offered
-
-
for: address
-
## Constants
Address already has an offer of this type.
const EOFFER_ALREADY_CREATED: u64 = 1;
An offer of the specified type for the account does not exist
const EOFFER_DNE_FOR_ACCOUNT: u64 = 0;
Address does not have an offer of this type to redeem.
const EOFFER_DOES_NOT_EXIST: u64 = 2;
## Function `create`
Publish a value of type Offered
under the sender's account. The value can be claimed by
either the for
address or the transaction sender.
public fun create<Offered: store>(account: &signer, offered: Offered, for: address)
Implementation
public fun create<Offered: store>(account: &signer, offered: Offered, for: address) {
assert!(!exists<Offer<Offered>>(signer::address_of(account)), error::already_exists(EOFFER_ALREADY_CREATED));
move_to(account, Offer<Offered> { offered, for });
}
Specification
Offer a struct to the account under address for
by
placing the offer under the signer's address
aborts_if exists<Offer<Offered>>(signer::address_of(account));
ensures exists<Offer<Offered>>(signer::address_of(account));
ensures global<Offer<Offered>>(signer::address_of(account)) == Offer<Offered> { offered: offered, for: for };
## Function `redeem`
Claim the value of type Offered
published at offer_address
.
Only succeeds if the sender is the intended recipient stored in for
or the original
publisher offer_address
.
Also fails if there is no Offer<Offered>
published.
public fun redeem<Offered: store>(account: &signer, offer_address: address): Offered
Implementation
public fun redeem<Offered: store>(account: &signer, offer_address: address): Offered acquires Offer {
assert!(exists<Offer<Offered>>(offer_address), error::not_found(EOFFER_DOES_NOT_EXIST));
let Offer<Offered> { offered, for } = move_from<Offer<Offered>>(offer_address);
let sender = signer::address_of(account);
assert!(sender == for || sender == offer_address, error::invalid_argument(EOFFER_DNE_FOR_ACCOUNT));
offered
}
Specification
Aborts if there is no offer under offer_address
or if the account
cannot redeem the offer.
Ensures that the offered struct under offer_address
is removed.
aborts_if !exists<Offer<Offered>>(offer_address);
aborts_if !is_allowed_recipient<Offered>(offer_address, signer::address_of(account));
ensures !exists<Offer<Offered>>(offer_address);
ensures result == old(global<Offer<Offered>>(offer_address).offered);
## Function `exists_at`
public fun exists_at<Offered: store>(offer_address: address): bool
Implementation
public fun exists_at<Offered: store>(offer_address: address): bool {
exists<Offer<Offered>>(offer_address)
}
Specification
aborts_if false;
Returns whether or not an Offer
resource is under the given address offer_address
.
ensures result == exists<Offer<Offered>>(offer_address);
## Function `address_of`
public fun address_of<Offered: store>(offer_address: address): address
Implementation
public fun address_of<Offered: store>(offer_address: address): address acquires Offer {
assert!(exists<Offer<Offered>>(offer_address), error::not_found(EOFFER_DOES_NOT_EXIST));
borrow_global<Offer<Offered>>(offer_address).for
}
Specification
Aborts is there is no offer resource Offer
at the offer_address
.
Returns the address of the intended recipient of the Offer
under the offer_address
.
aborts_if !exists<Offer<Offered>>(offer_address);
ensures result == global<Offer<Offered>>(offer_address).for;
## Module Specification
### Access Control
#### Creation of Offers
Says no offer is created for any address. Later, it is applied to all functions
except create
schema NoOfferCreated<Offered> {
ensures forall addr: address where !old(exists<Offer<Offered>>(addr)) : !exists<Offer<Offered>>(addr);
}
Apply OnlyCreateCanCreateOffer to every function except create
apply NoOfferCreated<Offered> to *<Offered>, * except create;
#### Removal of Offers
Says no offer is removed for any address. Applied below to everything except redeem
schema NoOfferRemoved<Offered> {
ensures forall addr: address where old(exists<Offer<Offered>>(addr)) :
(exists<Offer<Offered>>(addr) && global<Offer<Offered>>(addr) == old(global<Offer<Offered>>(addr)));
}
Only redeem
can remove an offer from the global store.
apply NoOfferRemoved<Offered> to *<Offered>, * except redeem;
### Helper Functions
Returns true if the recipient is allowed to redeem Offer<Offered>
at offer_address
and false otherwise.
fun is_allowed_recipient<Offered>(offer_addr: address, recipient: address): bool {
recipient == global<Offer<Offered>>(offer_addr).for || recipient == offer_addr
}