Skip to content

Commit

Permalink
add intent code
Browse files Browse the repository at this point in the history
  • Loading branch information
runtian-zhou committed Oct 31, 2024
1 parent 46f2c66 commit 80c0e36
Show file tree
Hide file tree
Showing 6 changed files with 330 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
intent-framework/build/
37 changes: 37 additions & 0 deletions aptos.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{ stdenv, fetchurl, lib, unzip }:

let
os =
if stdenv.isDarwin then "MacOSX"
else if stdenv.isLinux then "Ubuntu"
else throw "Unsupported platform ${stdenv.system}";

sha256 = if os == "MacOSX" then "sha256-6uVxfgcFgxgwF9vwQwaTt23IbV5vNACmjN9BO6TSQ20="
else "sha256-L/rpf/94WcK4Im5mgfFXypD16U+iVqkGmeOShx7MrH0=";

in stdenv.mkDerivation rec {
pname = "aptos-cli";
version = "3.1.0";

src = fetchurl {
url = "https://github.com/aptos-labs/aptos-core/releases/download/${pname}-v${version}/${pname}-${version}-${os}-x86_64.zip";
sha256 = sha256;
};

buildInputs = [ unzip ];

unpackPhase = ''
unzip $src
'';

installPhase = ''
mkdir -p $out/bin
cp aptos $out/bin/aptos
'';

meta = with lib; {
description = "Aptos CLI";
homepage = "https://github.com/aptos-labs/aptos-core";
platforms = [ "x86_64-darwin" "aarch64-darwin" "x86_64-linux" "aarch64-linux" ];
};
}
17 changes: 17 additions & 0 deletions intent-framework/Move.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "aptos-intent"
version = "1.0.0"
authors = []

[addresses]
aptos_intent = "_"

[dev-addresses]
aptos_intent = "0x123"

[dependencies.AptosFramework]
git = "https://github.com/aptos-labs/aptos-core.git"
rev = "main"
subdir = "aptos-move/framework/aptos-framework"

[dev-dependencies]
28 changes: 28 additions & 0 deletions intent-framework/shell.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
with import <nixpkgs> { };

pkgs.mkShell {
buildInputs = [
jq
nodePackages.nodemon
nodejs_18
(callPackage ../aptos.nix { })
];

shellHook = ''
alias gen="aptos init"
test() {
nodemon \
--ignore build/* \
--ext move \
--exec "aptos move test --dev --skip-fetch-latest-git-deps;"
}
pub() {
local intent=0x$(aptos config show-profiles | jq -r '.Result.default.account')
aptos move publish \
--named-addresses aptos_intent=$intent \
--skip-fetch-latest-git-deps
}
'';
}
134 changes: 134 additions & 0 deletions intent-framework/sources/fungible_asset_intent.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
module aptos_intent::fungible_asset_intent {
use std::error;
use aptos_framework::fungible_asset::{Self, FungibleAsset, Metadata, FungibleStore};
use aptos_intent::intent::{Self, TradeSession, TradeIntent};
use aptos_framework::object::{Self, DeleteRef, ExtendRef, Object};
use aptos_framework::primary_fungible_store;

/// The token offered is not the desired fungible asset.
const ENOT_DESIRED_TOKEN: u64 = 0;

/// The token offered does not meet amount requirement.
const EAMOUNT_NOT_MEET: u64 = 1;

struct FungibleStoreManager has store {
extend_ref: ExtendRef,
delete_ref: DeleteRef,
}

struct FungibleAssetLimitOrder has store, drop {
desired_metadata: Object<Metadata>,
desired_amount: u64,
issuer: address,
}

struct FungibleAssetRecipientWitness has drop {}

public fun create_fa_to_fa_intent(
source_fungible_asset: FungibleAsset,
desired_metadata: Object<Metadata>,
desired_amount: u64,
expiry_time: u64,
issuer: address,
): Object<TradeIntent<FungibleStoreManager, FungibleAssetLimitOrder>> {
let coin_store_ref = object::create_object(issuer);
let extend_ref = object::generate_extend_ref(&coin_store_ref);
let delete_ref = object::generate_delete_ref(&coin_store_ref);
let transfer_ref = object::generate_transfer_ref(&coin_store_ref);
let linear_ref = object::generate_linear_transfer_ref(&transfer_ref);
object::transfer_with_ref(linear_ref, object::address_from_constructor_ref(&coin_store_ref));

fungible_asset::create_store(&coin_store_ref, fungible_asset::metadata_from_asset(&source_fungible_asset));
fungible_asset::deposit(
object::object_from_constructor_ref<FungibleStore>(&coin_store_ref),
source_fungible_asset
);
intent::create_intent<FungibleStoreManager, FungibleAssetLimitOrder, FungibleAssetRecipientWitness>(
FungibleStoreManager { extend_ref, delete_ref},
FungibleAssetLimitOrder { desired_metadata, desired_amount, issuer },
expiry_time,
issuer,
FungibleAssetRecipientWitness {},
)
}

public fun start_fa_offering_session<Args: store + drop>(
intent: Object<TradeIntent<FungibleStoreManager, Args>>
): (FungibleAsset, TradeSession<Args>) {
let (store_manager, session) = intent::start_intent_session(intent);
let FungibleStoreManager { extend_ref, delete_ref } = store_manager;
let store_signer = object::generate_signer_for_extending(&extend_ref);
let fa_store = object::object_from_delete_ref<FungibleStore>(&delete_ref);
let fa = fungible_asset::withdraw(&store_signer, fa_store, fungible_asset::balance(fa_store));
fungible_asset::remove_store(&delete_ref);
object::delete(delete_ref);
(fa, session)
}

public fun finish_fa_receiving_session(
session: TradeSession<FungibleAssetLimitOrder>,
received_fa: FungibleAsset,
) {
let argument = intent::get_argument(&session);
assert!(
fungible_asset::metadata_from_asset(&received_fa) == argument.desired_metadata,
error::invalid_argument(ENOT_DESIRED_TOKEN)
);
assert!(
fungible_asset::amount(&received_fa) >= argument.desired_amount,
error::invalid_argument(EAMOUNT_NOT_MEET),
);

primary_fungible_store::deposit(argument.issuer, received_fa);
intent::finish_intent_session(session, FungibleAssetRecipientWitness {})
}

#[test(
aptos_framework = @0x1,
creator1 = @0xcafe,
creator2 = @0xcaff,
aaron = @0xface,
offerer = @0xbadd
)]
fun test_e2e_basic_flow(
aptos_framework: &signer,
creator1: &signer,
creator2: &signer,
aaron: &signer,
) {
use aptos_framework::timestamp;
use aptos_framework::signer;
use aptos_framework::primary_fungible_store;

timestamp::set_time_has_started_for_testing(aptos_framework);
let (mint_ref_1, _, burn_ref_1, _, test_token_1) = fungible_asset::create_fungible_asset(creator1);
let (creator_ref, metadata) = fungible_asset::create_test_token(creator2);
primary_fungible_store::init_test_metadata_with_primary_store_enabled(&creator_ref);
let mint_ref_2 = fungible_asset::generate_mint_ref(&creator_ref);
let test_token_2 = object::convert(metadata);

let fa1 = fungible_asset::mint(&mint_ref_1, 10);
// Register intent to trade 10 FA1 into 5 FA2.
let intent = create_fa_to_fa_intent(
fa1,
test_token_2,
5,
1,
signer::address_of(aaron),
);

let (fa1, session) = start_fa_offering_session(intent);

assert!(fungible_asset::metadata_from_asset(&fa1) == test_token_1, 1);
assert!(fungible_asset::amount(&fa1) == 10, 1);

// Mint FA2 for the sake of testing. In the real life we expect a DeFi app to perform the trade.
let fa2 = fungible_asset::mint(&mint_ref_2, 5);
fungible_asset::burn(&burn_ref_1, fa1);

// Trade FA1 for 5 FA2
finish_fa_receiving_session(session, fa2);

assert!(primary_fungible_store::balance(signer::address_of(aaron), test_token_2) == 5, 1);
}
}
113 changes: 113 additions & 0 deletions intent-framework/sources/intent.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
module aptos_intent::intent {
use std::error;
use std::signer;
use aptos_framework::object::{Self, DeleteRef, Object};
use aptos_framework::timestamp;
use aptos_framework::type_info::{Self, TypeInfo};

/// The offered intent has expired
const EINTENT_EXPIRED: u64 = 0;

/// The registered hook function for consuming resource doesn't match the type requirement.
const ECONSUMPTION_FUNCTION_TYPE_MISMATCH: u64 = 1;

/// Only owner can revoke an intent.
const ENOT_OWNER: u64 = 2;

/// Provided wrong witness to complete intent.
const EINVALID_WITNESS: u64 = 3;

struct TradeIntent<Source, Args> has key {
offered_resource: Source,
argument: Args,
self_delete_ref: DeleteRef,
expiry_time: u64,
witness_type: TypeInfo,
}

struct TradeSession<Args> {
argument: Args,
witness_type: TypeInfo,
}

// Core offering logic

public fun create_intent<Source: store, Args: store + drop, Witness: drop>(
offered_resource: Source,
argument: Args,
expiry_time: u64,
issuer: address,
_witness: Witness,
): Object<TradeIntent<Source, Args>> {
let constructor_ref = object::create_object(issuer);
let object_signer = object::generate_signer(&constructor_ref);
let self_delete_ref = object::generate_delete_ref(&constructor_ref);

move_to<TradeIntent<Source, Args>>(
&object_signer,
TradeIntent {
offered_resource,
argument,
expiry_time,
self_delete_ref,
witness_type: type_info::type_of<Witness>(),
}
);
object::object_from_constructor_ref(&constructor_ref)
}

public fun start_intent_session<Source: store, Args: store + drop>(
intent: Object<TradeIntent<Source, Args>>,
): (Source, TradeSession<Args>) acquires TradeIntent {
let intent_ref = borrow_global<TradeIntent<Source, Args>>(object::object_address(&intent));
assert!(timestamp::now_seconds() <= intent_ref.expiry_time, error::permission_denied(EINTENT_EXPIRED));

let TradeIntent {
offered_resource,
argument,
expiry_time: _,
self_delete_ref,
witness_type,
} = move_from<TradeIntent<Source, Args>>(object::object_address(&intent));

object::delete(self_delete_ref);

return (offered_resource, TradeSession {
argument,
witness_type,
})
}

public fun get_argument<Args>(session: &TradeSession<Args>): &Args {
&session.argument
}

public fun finish_intent_session<Witness: drop, Args: store + drop>(
session: TradeSession<Args>,
_witness: Witness,
) {
let TradeSession {
argument:_ ,
witness_type,
} = session;

assert!(type_info::type_of<Witness>() == witness_type, error::permission_denied(EINVALID_WITNESS));
}

public fun revoke_intent<Source: store, Args: store + drop>(
issuer: &signer,
intent: Object<TradeIntent<Source, Args>>,
): Source acquires TradeIntent {
assert!(object::owner(intent) == signer::address_of(issuer), error::permission_denied(ENOT_OWNER));
let TradeIntent {
offered_resource,
argument: _,
expiry_time: _,
self_delete_ref,
witness_type: _,
} = move_from<TradeIntent<Source, Args>>(object::object_address(&intent));

object::delete(self_delete_ref);
offered_resource
}
}

0 comments on commit 80c0e36

Please sign in to comment.