Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add intent code #2

Merged
merged 1 commit into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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-Uz3fkyj7SlBKKKi8bl3eNvQ6HiGnEE/UO362jiEWpTo=";

in stdenv.mkDerivation rec {
pname = "aptos-cli";
version = "4.3.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
}
}
Loading