Skip to content

Commit 911db1d

Browse files
add api test (aptos-labs#8948)
* add api test * add gas fee payer to ts sdk and add test * enable feature in genesis * lint * fmt * fmt
1 parent 65f0f73 commit 911db1d

File tree

8 files changed

+252
-11
lines changed

8 files changed

+252
-11
lines changed

api/src/tests/transactions_test.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ async fn test_multi_agent_signed_transaction() {
250250
.post("/transactions", resp)
251251
.await;
252252
}
253-
/* works when feature is enabled
253+
254254
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
255255
async fn test_fee_payer_signed_transaction() {
256256
let mut context = new_test_context(current_function_name!());
@@ -266,7 +266,8 @@ async fn test_fee_payer_signed_transaction() {
266266

267267
// Create a new account with a multi-agent signer
268268
let txn = root_account.sign_fee_payer_with_transaction_builder(
269-
vec![], &secondary,
269+
vec![],
270+
&secondary,
270271
factory.create_user_account(account.public_key()),
271272
);
272273

@@ -276,13 +277,13 @@ async fn test_fee_payer_signed_transaction() {
276277
.post_bcs_txn("/transactions", body)
277278
.await;
278279

279-
let (sender, secondary_signers,fee_payer_signer) = match txn.authenticator() {
280+
let (sender, _, fee_payer_signer) = match txn.authenticator() {
280281
TransactionAuthenticator::FeePayer {
281282
sender,
282283
secondary_signer_addresses: _,
283284
secondary_signers,
284285
fee_payer_address: _,
285-
fee_payer_signer
286+
fee_payer_signer,
286287
} => (sender, secondary_signers, fee_payer_signer),
287288
_ => panic!(
288289
"expecting TransactionAuthenticator::MultiAgent, but got: {:?}",
@@ -317,7 +318,7 @@ async fn test_fee_payer_signed_transaction() {
317318
.post("/transactions", resp)
318319
.await;
319320
}
320-
*/
321+
321322
#[ignore]
322323
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
323324
async fn test_multi_ed25519_signed_transaction() {

aptos-move/e2e-move-tests/src/tests/fee_payer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ fn test_two_to_two_transfer_fee_payer_is_sender() {
164164

165165
#[test]
166166
fn test_two_to_two_transfer_fee_payer_without_feature() {
167-
let mut h = MoveHarness::new();
167+
let mut h = MoveHarness::new_with_features(vec![], vec![FeatureFlag::GAS_PAYER_ENABLED]);
168168

169169
let alice = h.new_account_at(AccountAddress::from_hex_literal("0xa11ce").unwrap());
170170
let bob = h.new_account_at(AccountAddress::from_hex_literal("0xb0b").unwrap());

aptos-move/vm-genesis/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,7 @@ pub fn default_features() -> Vec<FeatureFlag> {
411411
FeatureFlag::BLS12_381_STRUCTURES,
412412
FeatureFlag::CHARGE_INVARIANT_VIOLATION,
413413
FeatureFlag::APTOS_UNIQUE_IDENTIFIERS,
414+
FeatureFlag::GAS_PAYER_ENABLED,
414415
]
415416
}
416417

ecosystem/typescript/sdk/src/aptos_types/authenticator.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ export abstract class TransactionAuthenticator {
1919
return TransactionAuthenticatorMultiEd25519.load(deserializer);
2020
case 2:
2121
return TransactionAuthenticatorMultiAgent.load(deserializer);
22+
case 3:
23+
return TransactionAuthenticatorFeePayer.load(deserializer);
2224
default:
2325
throw new Error(`Unknown variant index for TransactionAuthenticator: ${index}`);
2426
}
@@ -100,6 +102,36 @@ export class TransactionAuthenticatorMultiAgent extends TransactionAuthenticator
100102
}
101103
}
102104

105+
export class TransactionAuthenticatorFeePayer extends TransactionAuthenticator {
106+
constructor(
107+
public readonly sender: AccountAuthenticator,
108+
public readonly secondary_signer_addresses: Seq<AccountAddress>,
109+
public readonly secondary_signers: Seq<AccountAuthenticator>,
110+
public readonly fee_payer: { address: AccountAddress; authenticator: AccountAuthenticator },
111+
) {
112+
super();
113+
}
114+
115+
serialize(serializer: Serializer): void {
116+
serializer.serializeU32AsUleb128(3);
117+
this.sender.serialize(serializer);
118+
serializeVector<AccountAddress>(this.secondary_signer_addresses, serializer);
119+
serializeVector<AccountAuthenticator>(this.secondary_signers, serializer);
120+
this.fee_payer.address.serialize(serializer);
121+
this.fee_payer.authenticator.serialize(serializer);
122+
}
123+
124+
static load(deserializer: Deserializer): TransactionAuthenticatorMultiAgent {
125+
const sender = AccountAuthenticator.deserialize(deserializer);
126+
const secondary_signer_addresses = deserializeVector(deserializer, AccountAddress);
127+
const secondary_signers = deserializeVector(deserializer, AccountAuthenticator);
128+
const address = AccountAddress.deserialize(deserializer);
129+
const authenticator = AccountAuthenticator.deserialize(deserializer);
130+
const fee_payer = { address, authenticator };
131+
return new TransactionAuthenticatorFeePayer(sender, secondary_signer_addresses, secondary_signers, fee_payer);
132+
}
133+
}
134+
103135
export abstract class AccountAuthenticator {
104136
abstract serialize(serializer: Serializer): void;
105137

ecosystem/typescript/sdk/src/aptos_types/transaction.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
Uint16,
2222
Uint256,
2323
} from "../bcs";
24-
import { TransactionAuthenticator } from "./authenticator";
24+
import { AccountAuthenticator, TransactionAuthenticator, TransactionAuthenticatorMultiAgent } from "./authenticator";
2525
import { Identifier } from "./identifier";
2626
import { TypeTag } from "./type_tag";
2727
import { AccountAddress } from "./account_address";
@@ -371,6 +371,8 @@ export abstract class RawTransactionWithData {
371371
switch (index) {
372372
case 0:
373373
return MultiAgentRawTransaction.load(deserializer);
374+
case 1:
375+
return FeePayerRawTransaction.load(deserializer);
374376
default:
375377
throw new Error(`Unknown variant index for RawTransactionWithData: ${index}`);
376378
}
@@ -400,6 +402,32 @@ export class MultiAgentRawTransaction extends RawTransactionWithData {
400402
}
401403
}
402404

405+
export class FeePayerRawTransaction extends RawTransactionWithData {
406+
constructor(
407+
public readonly raw_txn: RawTransaction,
408+
public readonly secondary_signer_addresses: Seq<AccountAddress>,
409+
public readonly fee_payer_address: AccountAddress,
410+
) {
411+
super();
412+
}
413+
414+
serialize(serializer: Serializer): void {
415+
// enum variant index
416+
serializer.serializeU32AsUleb128(1);
417+
this.raw_txn.serialize(serializer);
418+
serializeVector<TransactionArgument>(this.secondary_signer_addresses, serializer);
419+
this.fee_payer_address.serialize(serializer);
420+
}
421+
422+
static load(deserializer: Deserializer): FeePayerRawTransaction {
423+
const rawTxn = RawTransaction.deserialize(deserializer);
424+
const secondarySignerAddresses = deserializeVector(deserializer, AccountAddress);
425+
const feePayerAddress = AccountAddress.deserialize(deserializer);
426+
427+
return new FeePayerRawTransaction(rawTxn, secondarySignerAddresses, feePayerAddress);
428+
}
429+
}
430+
403431
export abstract class TransactionPayload {
404432
abstract serialize(serializer: Serializer): void;
405433

ecosystem/typescript/sdk/src/plugins/token_client.ts

Lines changed: 82 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -291,12 +291,12 @@ export class TokenClient {
291291
* Directly transfer the specified amount of tokens from account to receiver
292292
* using a single multi signature transaction.
293293
*
294-
* @param sender AptosAccount where token from which tokens will be transfered
295-
* @param receiver Hex-encoded 32 byte Aptos account address to which tokens will be transfered
294+
* @param sender AptosAccount where token from which tokens will be transferred
295+
* @param receiver Hex-encoded 32 byte Aptos account address to which tokens will be transferred
296296
* @param creator Hex-encoded 32 byte Aptos account address to which created tokens
297297
* @param collectionName Name of collection where token is stored
298298
* @param name Token name
299-
* @param amount Amount of tokens which will be transfered
299+
* @param amount Amount of tokens which will be transferred
300300
* @param property_version the version of token PropertyMap with a default value 0.
301301
* @returns The hash of the transaction submitted to the API
302302
*/
@@ -352,6 +352,85 @@ export class TokenClient {
352352
return transactionRes.hash;
353353
}
354354

355+
/**
356+
* Directly transfer the specified amount of tokens from account to receiver
357+
* using a single multi signature transaction.
358+
*
359+
* @param sender AptosAccount where token from which tokens will be transferred
360+
* @param receiver Hex-encoded 32 byte Aptos account address to which tokens will be transferred
361+
* @param creator Hex-encoded 32 byte Aptos account address to which created tokens
362+
* @param collectionName Name of collection where token is stored
363+
* @param name Token name
364+
* @param amount Amount of tokens which will be transferred
365+
* @param fee_payer AptosAccount which will pay fee for transaction
366+
* @param property_version the version of token PropertyMap with a default value 0.
367+
* @returns The hash of the transaction submitted to the API
368+
*/
369+
async directTransferTokenWithFeePayer(
370+
sender: AptosAccount,
371+
receiver: AptosAccount,
372+
creator: MaybeHexString,
373+
collectionName: string,
374+
name: string,
375+
amount: AnyNumber,
376+
fee_payer: AptosAccount,
377+
propertyVersion: AnyNumber = 0,
378+
extraArgs?: OptionalTransactionArgs,
379+
): Promise<string> {
380+
const builder = new TransactionBuilderRemoteABI(this.aptosClient, { sender: sender.address(), ...extraArgs });
381+
const rawTxn = await builder.build(
382+
"0x3::token::direct_transfer_script",
383+
[],
384+
[creator, collectionName, name, propertyVersion, amount],
385+
);
386+
387+
const feePayerTxn = new TxnBuilderTypes.FeePayerRawTransaction(
388+
rawTxn,
389+
[TxnBuilderTypes.AccountAddress.fromHex(receiver.address())],
390+
TxnBuilderTypes.AccountAddress.fromHex(fee_payer.address()),
391+
);
392+
393+
const senderSignature = new TxnBuilderTypes.Ed25519Signature(
394+
sender.signBuffer(TransactionBuilder.getSigningMessage(feePayerTxn)).toUint8Array(),
395+
);
396+
397+
const senderAuthenticator = new TxnBuilderTypes.AccountAuthenticatorEd25519(
398+
new TxnBuilderTypes.Ed25519PublicKey(sender.signingKey.publicKey),
399+
senderSignature,
400+
);
401+
402+
const receiverSignature = new TxnBuilderTypes.Ed25519Signature(
403+
receiver.signBuffer(TransactionBuilder.getSigningMessage(feePayerTxn)).toUint8Array(),
404+
);
405+
406+
const receiverAuthenticator = new TxnBuilderTypes.AccountAuthenticatorEd25519(
407+
new TxnBuilderTypes.Ed25519PublicKey(receiver.signingKey.publicKey),
408+
receiverSignature,
409+
);
410+
411+
const feePayerSignature = new TxnBuilderTypes.Ed25519Signature(
412+
fee_payer.signBuffer(TransactionBuilder.getSigningMessage(feePayerTxn)).toUint8Array(),
413+
);
414+
415+
const feePayerAuthenticator = new TxnBuilderTypes.AccountAuthenticatorEd25519(
416+
new TxnBuilderTypes.Ed25519PublicKey(fee_payer.signingKey.publicKey),
417+
feePayerSignature,
418+
);
419+
420+
const txAuthenticatorFeePayer = new TxnBuilderTypes.TransactionAuthenticatorFeePayer(
421+
senderAuthenticator,
422+
[TxnBuilderTypes.AccountAddress.fromHex(receiver.address())],
423+
[receiverAuthenticator],
424+
{ address: TxnBuilderTypes.AccountAddress.fromHex(fee_payer.address()), authenticator: feePayerAuthenticator },
425+
);
426+
427+
const bcsTxn = bcsToBytes(new TxnBuilderTypes.SignedTransaction(rawTxn, txAuthenticatorFeePayer));
428+
429+
const transactionRes = await this.aptosClient.submitSignedBCSTransaction(bcsTxn);
430+
431+
return transactionRes.hash;
432+
}
433+
355434
/**
356435
* User opt-in or out direct transfer through a boolean flag
357436
*

ecosystem/typescript/sdk/src/tests/e2e/aptos_client.test.ts

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,103 @@ test(
462462
longTestTimeout,
463463
);
464464

465+
test(
466+
"submits multiagent transaction with fee payer",
467+
async () => {
468+
const client = new AptosClient(NODE_URL);
469+
const faucetClient = getFaucetClient();
470+
const tokenClient = new TokenClient(client);
471+
472+
const alice = new AptosAccount();
473+
const bob = new AptosAccount();
474+
475+
// Fund both Alice's and Bob's Account
476+
await faucetClient.fundAccount(alice.address(), 100000000);
477+
await faucetClient.fundAccount(bob.address(), 100000000);
478+
479+
const collectionName = "AliceCollection";
480+
const tokenName = "Alice Token";
481+
482+
async function ensureTxnSuccess(txnHashPromise: Promise<string>) {
483+
const txnHash = await txnHashPromise;
484+
const txn = await client.waitForTransactionWithResult(txnHash);
485+
expect((txn as any)?.success).toBe(true);
486+
}
487+
488+
// Create collection and token on Alice's account
489+
await ensureTxnSuccess(
490+
tokenClient.createCollection(alice, collectionName, "Alice's simple collection", "https://aptos.dev"),
491+
);
492+
493+
await ensureTxnSuccess(
494+
tokenClient.createToken(
495+
alice,
496+
collectionName,
497+
tokenName,
498+
"Alice's simple token",
499+
1,
500+
"https://aptos.dev/img/nyan.jpeg",
501+
1000,
502+
alice.address(),
503+
0,
504+
0,
505+
["key"],
506+
["2"],
507+
["u64"],
508+
),
509+
);
510+
511+
const propertyVersion = 0;
512+
const tokenId = {
513+
token_data_id: {
514+
creator: alice.address().hex(),
515+
collection: collectionName,
516+
name: tokenName,
517+
},
518+
property_version: `${propertyVersion}`,
519+
};
520+
521+
// Transfer Token from Alice's Account to Bob's Account with bob paying the fee
522+
await tokenClient.getCollectionData(alice.address().hex(), collectionName);
523+
let aliceBalance = await tokenClient.getTokenForAccount(alice.address().hex(), tokenId);
524+
expect(aliceBalance.amount).toBe("1");
525+
526+
const getBalance = async (account: AptosAccount) => {
527+
const resources = await client.getAccountResources(account.address().hex());
528+
let accountResource = resources.find((r) => r.type === aptosCoin);
529+
return BigInt((accountResource!.data as any).coin.value);
530+
};
531+
532+
const aliceBefore = await getBalance(alice);
533+
const bobBefore = await getBalance(bob);
534+
535+
const txnHash = await tokenClient.directTransferTokenWithFeePayer(
536+
alice,
537+
bob,
538+
alice.address(),
539+
collectionName,
540+
tokenName,
541+
1,
542+
bob,
543+
propertyVersion,
544+
undefined,
545+
);
546+
547+
await client.waitForTransaction(txnHash, { checkSuccess: true });
548+
549+
aliceBalance = await tokenClient.getTokenForAccount(alice.address().hex(), tokenId);
550+
expect(aliceBalance.amount).toBe("0");
551+
552+
const bobBalance = await tokenClient.getTokenForAccount(bob.address().hex(), tokenId);
553+
expect(bobBalance.amount).toBe("1");
554+
555+
// Check that Alice did not pay the fee
556+
expect(await getBalance(alice)).toBe(aliceBefore);
557+
// Check that Bob paid the fee
558+
expect(await getBalance(bob)).toBeLessThan(bobBefore);
559+
},
560+
longTestTimeout,
561+
);
465562
test(
466563
"publishes a package",
467564
async () => {

ecosystem/typescript/sdk/src/transaction_builder/builder.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
TransactionAuthenticatorMultiEd25519,
1414
SigningMessage,
1515
MultiAgentRawTransaction,
16+
FeePayerRawTransaction,
1617
AccountAddress,
1718
EntryFunction,
1819
Identifier,
@@ -42,7 +43,7 @@ export { TypeTagParser } from "../aptos_types";
4243
const RAW_TRANSACTION_SALT = "APTOS::RawTransaction";
4344
const RAW_TRANSACTION_WITH_DATA_SALT = "APTOS::RawTransactionWithData";
4445

45-
type AnyRawTransaction = RawTransaction | MultiAgentRawTransaction;
46+
type AnyRawTransaction = RawTransaction | MultiAgentRawTransaction | FeePayerRawTransaction;
4647

4748
/**
4849
* Function that takes in a Signing Message (serialized raw transaction)
@@ -78,6 +79,8 @@ export class TransactionBuilder<F extends SigningFn> {
7879
hash.update(RAW_TRANSACTION_SALT);
7980
} else if (rawTxn instanceof MultiAgentRawTransaction) {
8081
hash.update(RAW_TRANSACTION_WITH_DATA_SALT);
82+
} else if (rawTxn instanceof FeePayerRawTransaction) {
83+
hash.update(RAW_TRANSACTION_WITH_DATA_SALT);
8184
} else {
8285
throw new Error("Unknown transaction type.");
8386
}

0 commit comments

Comments
 (0)