-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
46f2c66
commit 5096b81
Showing
6 changed files
with
330 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
intent-framework/build/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" ]; | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
''; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |