From e156c812bb44292ec9d23cf4d44a2283aba22526 Mon Sep 17 00:00:00 2001 From: Christian Rogobete Date: Thu, 30 Mar 2023 17:19:13 +0200 Subject: [PATCH] improve soroban auth testing --- lib/src/soroban/soroban_auth.dart | 79 ++++- soroban.md | 4 +- test/query_test.dart | 4 +- test/soroban_test_atomic_swap.dart | 497 +++++++++++++++++++++++++++++ test/soroban_test_auth.dart | 152 --------- test/wasm/atomic_swap.wasm | Bin 0 -> 1497 bytes test/wasm/token.wasm | Bin 0 -> 5656 bytes 7 files changed, 571 insertions(+), 165 deletions(-) create mode 100644 test/soroban_test_atomic_swap.dart create mode 100644 test/wasm/atomic_swap.wasm create mode 100644 test/wasm/token.wasm diff --git a/lib/src/soroban/soroban_auth.dart b/lib/src/soroban/soroban_auth.dart index 7602537..9ecbbbc 100644 --- a/lib/src/soroban/soroban_auth.dart +++ b/lib/src/soroban/soroban_auth.dart @@ -4,7 +4,6 @@ import 'dart:convert'; import 'dart:typed_data'; - import '../key_pair.dart'; import '../network.dart'; import '../util.dart'; @@ -14,27 +13,57 @@ import '../xdr/xdr_type.dart'; import '../xdr/xdr_data_io.dart'; /// Represents a single address in the Stellar network. +/// /// An address can represent an account or a contract. +/// To create an address, call [Address.new] +/// or use [Address.forAccountId] to create an Address for a given accountId +/// or use [Address.forContractId] to create an Address for a given contractId +/// or use [Address.fromXdr] to create an Address for a given [XdrSCAddress]. class Address { + static const int TYPE_ACCOUNT = 0; static const int TYPE_CONTRACT = 1; int _type; + + /// The type of the Address (TYPE_ACCOUNT or TYPE_CONTRACT). get type => _type; + /// The id of the account if type is TYPE_ACCOUNT otherwise null. String? accountId; + + /// The id of the contract if type is TYPE_CONTRACT otherwise null. String? contractId; - Address(this._type, {this.accountId, this.contractId}); + /// Constructs an [Address] for the given [type] which can be [Address.TYPE_ACCOUNT] or [Address.TYPE_CONTRACT]. + /// + /// If [Address.TYPE_ACCOUNT] one must provide [accountId]. + /// If [Address.TYPE_CONTRACT] one must provide [contractId]. + Address(this._type, {this.accountId, this.contractId}) { + if (this._type != TYPE_ACCOUNT && this._type != TYPE_CONTRACT) { + throw new Exception("unknown type"); + } + + if (this._type == TYPE_ACCOUNT && this.accountId == null) { + throw new Exception("invalid arguments"); + } + + if (this._type == TYPE_CONTRACT && this.contractId == null) { + throw new Exception("invalid arguments"); + } + } + /// Constructs an [Address] of type [Address.TYPE_ACCOUNT] for the given [accountId]. static Address forAccountId(String accountId) { return Address(TYPE_ACCOUNT, accountId: accountId); } + /// Constructs an [Address] of type [Address.TYPE_CONTRACT] for the given [contractId]. static Address forContractId(String contractId) { return Address(TYPE_CONTRACT, contractId: contractId); } + /// Constructs an [Address] from the given [xdr]. static Address fromXdr(XdrSCAddress xdr) { if (xdr.discriminant == XdrSCAddressType.SC_ADDRESS_TYPE_ACCOUNT) { KeyPair kp = KeyPair.fromXdrPublicKey(xdr.accountId!.accountID); @@ -47,6 +76,7 @@ class Address { } } + /// Returns a [XdrSCAddress] object created from this [Address] object. XdrSCAddress toXdr() { if (_type == TYPE_ACCOUNT) { if (accountId == null) { @@ -63,23 +93,35 @@ class Address { } } + /// Returns a [XdrSCVal] containing an [XdrSCObject] for this [Address]. XdrSCVal toXdrSCVal() { return XdrSCVal.forObject(XdrSCObject.forAddress(toXdr())); } } /// Represents an authorized invocation. +/// /// See Soroban Documentation - Authorization for more information. class AuthorizedInvocation { - String contractId; // The ID of the contract to invoke. - String functionName; // The name of the function to invoke. - List args = List.empty( - growable: - true); // The arguments to pass to the function. array of XdrSCVal. + + /// The id of the contract to invoke. + String contractId; + + /// The name of the contract function to invoke. + String functionName; + + /// The list of arguments to pass to the contract function to be called. + List args = List.empty(growable: true); + + /// The list of sub-invocations to pass to the contract function to be called. List subInvocations = List.empty( - growable: - true); // The sub-invocations to pass to the function. array of AuthorizedInvocation. + growable:true); + /// Constructs an [AuthorizedInvocation] object for the given [contractId] + /// and [functionName] of the contract function to be called. + /// + /// Optional list of [args] for the contract function to be called + /// and optional list of [subInvocations] can be provided. AuthorizedInvocation(this.contractId, this.functionName, {List? args, List? subInvocations}) { if (args != null) { @@ -90,6 +132,7 @@ class AuthorizedInvocation { } } + /// Constructs an [AuthorizedInvocation] from the given [xdr]. static AuthorizedInvocation fromXdr(XdrAuthorizedInvocation xdr) { List subInvocations = List.empty(growable: true); @@ -101,6 +144,7 @@ class AuthorizedInvocation { args: xdr.args, subInvocations: subInvocations); } + /// Returns an [XdrAuthorizedInvocation] object created from this [AuthorizedInvocation] object. XdrAuthorizedInvocation toXdr() { List xdrSubs = List.empty(growable: true); @@ -113,10 +157,17 @@ class AuthorizedInvocation { } /// Represents a contract authorization. +/// /// See Soroban Documentation - Authorization for more information. class ContractAuth { + + /// The root authorized invocation. AuthorizedInvocation rootInvocation; + + /// The signature arguments. List signatureArgs = List.empty(growable: true); + + Address? address; int? nonce; @@ -127,7 +178,9 @@ class ContractAuth { } } - /// Sign the contract authorization, the signature will be added to the `signatureArgs` + /// Signs the contract authorization. + /// + /// The signature will be added to the [signatureArgs] /// For custom accounts, this signature format may not be applicable. /// See Soroban Documentation - Stellar Account Signatures void sign(KeyPair signer, Network network) { @@ -150,12 +203,14 @@ class ContractAuth { signatureArgs.add(signature.toXdrSCVal()); } + /// Constructs a [ContractAuth] from base64 encoded [xdr]. static ContractAuth fromBase64EncodedXdr(String xdr) { Uint8List bytes = base64Decode(xdr); return ContractAuth.fromXdr( XdrContractAuth.decode(XdrDataInputStream(bytes))); } + /// Constructs a [ContractAuth] from [xdr]. static ContractAuth fromXdr(XdrContractAuth xdr) { AuthorizedInvocation rootInvocation = AuthorizedInvocation.fromXdr(xdr.rootInvocation); @@ -180,6 +235,7 @@ class ContractAuth { signatureArgs: argsArr, address: address, nonce: nonce); } + /// Constructs a list of [ContractAuth] from a list of [XdrContractAuth]. static List fromXdrList(List xdrAuth) { List result = List.empty(growable: true); for (XdrContractAuth next in xdrAuth) { @@ -188,6 +244,7 @@ class ContractAuth { return result; } + /// Creates an [XdrContractAuth] from this. XdrContractAuth toXdr() { XdrAddressWithNonce? addressWithNonce; if (address != null && nonce != null) { @@ -206,6 +263,7 @@ class ContractAuth { addressWithNonce, rootInvocation.toXdr(), sigArgs); } + /// Creates a list of [XdrContractAuth] from a list of [ContractAuth]. static List toXdrList(List auth) { List result = List.empty(growable: true); for (ContractAuth next in auth) { @@ -215,6 +273,7 @@ class ContractAuth { } } +/// Represents a signature used by [ContractAuth]. class AccountEd25519Signature { XdrPublicKey publicKey; Uint8List signatureBytes; diff --git a/soroban.md b/soroban.md index d5b366f..6bf156b 100644 --- a/soroban.md +++ b/soroban.md @@ -374,7 +374,9 @@ SimulateTransactionResponse simulateResponse = The example above invokes this assembly script [auth contract](https://github.com/Soneso/as-soroban-examples/tree/main/auth#code). -Other examples like [flutter atomic swap](https://github.com/Soneso/stellar_flutter_sdk/blob/master/test/soroban_test_auth.dart#L467) can be found in the [Soroban Auth Test Cases](https://github.com/Soneso/stellar_flutter_sdk/blob/master/test/soroban_test_auth.dart) of the SDK. +Other auth examples can be found in the [Soroban Auth Test Cases](https://github.com/Soneso/stellar_flutter_sdk/blob/master/test/soroban_test_auth.dart) of the SDK. + +An advanced auth example can be found in the [flutter atomic swap](https://github.com/Soneso/stellar_flutter_sdk/blob/master/test/soroban_test_atomic_swap.dart) test. #### Get Events diff --git a/test/query_test.dart b/test/query_test.dart index 92a7342..1442c92 100644 --- a/test/query_test.dart +++ b/test/query_test.dart @@ -182,7 +182,7 @@ void main() { /// ! get Claimable Balance ID from BID result at claimable_balance_test.dart Page operationsPage = await sdk.operations .forClaimableBalance( - "00000000b8860382915f2a621bd0e9290a6a4d7753e499cdca1cfba7f4d170d209675840") + "00000000d27ed397b3d3c74e6ced223e26c000029889ac0501f5fae11bf647657a749762") .limit(1) .order(RequestBuilderOrder.DESC) .execute(); @@ -195,7 +195,7 @@ void main() { /// ! get Claimable Balance ID from BID result at claimable_balance_test.dart Page transactionsPage = await sdk.transactions .forClaimableBalance( - "00000000b8860382915f2a621bd0e9290a6a4d7753e499cdca1cfba7f4d170d209675840") + "00000000d27ed397b3d3c74e6ced223e26c000029889ac0501f5fae11bf647657a749762") .limit(1) .order(RequestBuilderOrder.DESC) .execute(); diff --git a/test/soroban_test_atomic_swap.dart b/test/soroban_test_atomic_swap.dart new file mode 100644 index 0000000..14632fd --- /dev/null +++ b/test/soroban_test_atomic_swap.dart @@ -0,0 +1,497 @@ +import 'dart:typed_data'; +import 'dart:convert'; + +import 'package:convert/convert.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:stellar_flutter_sdk/stellar_flutter_sdk.dart'; + +void main() { + SorobanServer sorobanServer = + SorobanServer("https://horizon-futurenet.stellar.cash/soroban/rpc"); + + KeyPair adminKeypair = KeyPair.random(); + String adminId = adminKeypair.accountId; + KeyPair aliceKeypair = KeyPair.random(); + String aliceId = aliceKeypair.accountId; + KeyPair bobKeypair = KeyPair.random(); + String bobId = bobKeypair.accountId; + + String tokenContractPath = + "/Users/chris/Soneso/github/stellar_flutter_sdk/test/wasm/token.wasm"; + String swapContractPath = + "/Users/chris/Soneso/github/stellar_flutter_sdk/test/wasm/atomic_swap.wasm"; + String? tokenAContractWasmId; + String? tokenAContractId; + String? tokenBContractWasmId; + String? tokenBContractId; + String? swapContractWasmId; + String? swapContractId; + + setUp(() async { + sorobanServer.enableLogging = false; + sorobanServer.acknowledgeExperimental = true; + GetAccountResponse accountResponse = + await sorobanServer.getAccount(adminId); + if (accountResponse.accountMissing) { + await FuturenetFriendBot.fundTestAccount(adminId); + print("admin " + adminId + " : " + adminKeypair.secretSeed); + } + await sorobanServer.getAccount(aliceId); + if (accountResponse.accountMissing) { + await FuturenetFriendBot.fundTestAccount(aliceId); + print("alice " + aliceId + " : " + aliceKeypair.secretSeed); + } + await sorobanServer.getAccount(bobId); + if (accountResponse.accountMissing) { + await FuturenetFriendBot.fundTestAccount(bobId); + print("bob " + bobId + " : " + bobKeypair.secretSeed); + } + }); + + // poll until success or error + Future pollStatus(String transactionId) async { + var status = SorobanServer.TRANSACTION_STATUS_PENDING; + GetTransactionStatusResponse? statusResponse; + while (status == SorobanServer.TRANSACTION_STATUS_PENDING) { + await Future.delayed(const Duration(seconds: 3), () {}); + statusResponse = await sorobanServer.getTransactionStatus(transactionId); + assert(statusResponse.error == null); + status = statusResponse.status!; + if (status == SorobanServer.TRANSACTION_STATUS_ERROR) { + assert(statusResponse.resultError != null); + print(statusResponse.resultError?.message); + assert(false); + } else if (status == SorobanServer.TRANSACTION_STATUS_SUCCESS) { + assert(statusResponse.results != null); + assert(statusResponse.results!.isNotEmpty); + } + } + return statusResponse!; + } + + Future installContract(String contractCodePath) async { + // load account + GetAccountResponse submitter = + await sorobanServer.getAccount(adminId); + assert(!submitter.isErrorResponse); + + // load contract wasm file + Uint8List contractCode = await Util.readFile(contractCodePath); + + // create transaction for installing the contract + InvokeHostFunctionOperation operation = + InvokeHostFuncOpBuilder.forInstallingContractCode(contractCode) + .build(); + Transaction transaction = + new TransactionBuilder(submitter).addOperation(operation).build(); + + // simulate first to obtain the footprint + SimulateTransactionResponse simulateResponse = + await sorobanServer.simulateTransaction(transaction); + assert(!simulateResponse.isErrorResponse); + assert(simulateResponse.footprint != null); + + // set footprint and sign transaction + transaction.setFootprint(simulateResponse.footprint!); + transaction.sign(adminKeypair, Network.FUTURENET); + + // check transaction xdr encoding and decoding back and forth + String transactionEnvelopeXdr = transaction.toEnvelopeXdrBase64(); + assert(transactionEnvelopeXdr == + AbstractTransaction.fromEnvelopeXdrString(transactionEnvelopeXdr) + .toEnvelopeXdrBase64()); + + // send transaction to soroban rpc server + SendTransactionResponse sendResponse = + await sorobanServer.sendTransaction(transaction); + assert(!sendResponse.isErrorResponse); + assert(sendResponse.resultError == null); + + GetTransactionStatusResponse statusResponse = + await pollStatus(sendResponse.transactionId!); + String? wasmId = statusResponse.getWasmId(); + assert(wasmId != null); + return wasmId!; + } + + Future createContract(String wasmId) async { + + // reload account for current sequence nr + GetAccountResponse submitter = + await sorobanServer.getAccount(adminId); + assert(!submitter.isErrorResponse); + + // build the operation for creating the contract + InvokeHostFunctionOperation operation = + InvokeHostFuncOpBuilder.forCreatingContract(wasmId) + .build(); + + // build the transaction for creating the contract + Transaction transaction = + new TransactionBuilder(submitter).addOperation(operation).build(); + + // first simulate to obtain the footprint + SimulateTransactionResponse simulateResponse = + await sorobanServer.simulateTransaction(transaction); + assert(!simulateResponse.isErrorResponse); + assert(simulateResponse.resultError == null); + + // set footprint & sign + transaction.setFootprint(simulateResponse.footprint!); + transaction.sign(adminKeypair, Network.FUTURENET); + + // send transaction to soroban rpc server + SendTransactionResponse sendResponse = + await sorobanServer.sendTransaction(transaction); + assert(!sendResponse.isErrorResponse); + assert(sendResponse.resultError == null); + + GetTransactionStatusResponse statusResponse = + await pollStatus(sendResponse.transactionId!); + String? contractId = statusResponse.getContractId(); + assert(contractId != null); + return contractId!; + } + + Future createToken(String contractId, String name, String symbol) async { + // see https://soroban.stellar.org/docs/reference/interfaces/token-interface + // reload account for sequence number + GetAccountResponse invoker = await sorobanServer.getAccount(adminId); + assert(!invoker.isErrorResponse); + + Address adminAddress = Address.forAccountId(adminId); + String functionName = "initialize"; + List list = utf8.encode(name); + String nameHex = hex.encode(list); + XdrSCVal tokenName = XdrSCVal.forObject(XdrSCObject.forBytes(Util.hexToBytes(nameHex))); + list = utf8.encode(name); + String symbolHex = hex.encode(list); + XdrSCVal tokenSymbol = XdrSCVal.forObject(XdrSCObject.forBytes(Util.hexToBytes(symbolHex))); + + List args = [adminAddress.toXdrSCVal(), XdrSCVal.forU32(8), tokenName, tokenSymbol]; + + AuthorizedInvocation rootInvocation = + AuthorizedInvocation(contractId, functionName, args: args); + + ContractAuth contractAuth = ContractAuth(rootInvocation); + + InvokeHostFunctionOperation operation = + InvokeHostFuncOpBuilder.forInvokingContract( + contractId, functionName, + functionArguments: args, contractAuth: [contractAuth]).build(); + Transaction transaction = + new TransactionBuilder(invoker).addOperation(operation).build(); + + // simulate first to get footprint + SimulateTransactionResponse simulateResponse = + await sorobanServer.simulateTransaction(transaction); + assert(simulateResponse.error == null); + assert(simulateResponse.resultError == null); + assert(simulateResponse.footprint != null); + + // set footprint and sign + transaction.setFootprint(simulateResponse.footprint!); + transaction.sign(adminKeypair, Network.FUTURENET); + + // check transaction xdr encoding and decoding back and forth + String transactionEnvelopeXdr = transaction.toEnvelopeXdrBase64(); + assert(transactionEnvelopeXdr == + AbstractTransaction.fromEnvelopeXdrString(transactionEnvelopeXdr) + .toEnvelopeXdrBase64()); + + // send the transaction + SendTransactionResponse sendResponse = + await sorobanServer.sendTransaction(transaction); + assert(sendResponse.error == null); + assert(sendResponse.resultError == null); + + GetTransactionStatusResponse statusResponse = + await pollStatus(sendResponse.transactionId!); + String status = statusResponse.status!; + assert(status == SorobanServer.TRANSACTION_STATUS_SUCCESS); + } + + Future mint(String contractId, String toAccountId, int amount) async { + // see https://soroban.stellar.org/docs/reference/interfaces/token-interface + // reload account for sequence number + GetAccountResponse invoker = await sorobanServer.getAccount(adminId); + assert(!invoker.isErrorResponse); + + Address adminAddress = Address.forAccountId(adminId); + Address toAddress = Address.forAccountId(toAccountId); + XdrSCVal amountVal = XdrSCVal.forObject( + XdrSCObject.forI128(XdrInt128Parts.forLoHi(amount, 0))); + String functionName = "mint"; + + List args = [adminAddress.toXdrSCVal(), toAddress.toXdrSCVal(), amountVal]; + + AuthorizedInvocation rootInvocation = + AuthorizedInvocation(contractId, functionName, args: args); + + ContractAuth contractAuth = ContractAuth(rootInvocation); + + InvokeHostFunctionOperation operation = + InvokeHostFuncOpBuilder.forInvokingContract( + contractId, functionName, + functionArguments: args, contractAuth: [contractAuth]).build(); + Transaction transaction = + new TransactionBuilder(invoker).addOperation(operation).build(); + + // simulate first to get footprint + SimulateTransactionResponse simulateResponse = + await sorobanServer.simulateTransaction(transaction); + assert(simulateResponse.error == null); + assert(simulateResponse.resultError == null); + assert(simulateResponse.footprint != null); + + // set footprint and sign + transaction.setFootprint(simulateResponse.footprint!); + transaction.sign(adminKeypair, Network.FUTURENET); + + // check transaction xdr encoding and decoding back and forth + String transactionEnvelopeXdr = transaction.toEnvelopeXdrBase64(); + assert(transactionEnvelopeXdr == + AbstractTransaction.fromEnvelopeXdrString(transactionEnvelopeXdr) + .toEnvelopeXdrBase64()); + + // send the transaction + SendTransactionResponse sendResponse = + await sorobanServer.sendTransaction(transaction); + assert(sendResponse.error == null); + assert(sendResponse.resultError == null); + + GetTransactionStatusResponse statusResponse = + await pollStatus(sendResponse.transactionId!); + String status = statusResponse.status!; + assert(status == SorobanServer.TRANSACTION_STATUS_SUCCESS); + } + + Futurebalance(String contractId, String accountId) async { + + // reload account for sequence number + GetAccountResponse invoker = await sorobanServer.getAccount(adminId); + assert(!invoker.isErrorResponse); + + + Address address = Address.forAccountId(accountId); + String functionName = "balance"; + + List args = [address.toXdrSCVal()]; + + InvokeHostFunctionOperation operation = + InvokeHostFuncOpBuilder.forInvokingContract( + contractId, functionName, + functionArguments: args).build(); + Transaction transaction = + new TransactionBuilder(invoker).addOperation(operation).build(); + + // simulate first to get footprint + SimulateTransactionResponse simulateResponse = + await sorobanServer.simulateTransaction(transaction); + assert(simulateResponse.error == null); + assert(simulateResponse.resultError == null); + assert(simulateResponse.footprint != null); + + // set footprint and sign + transaction.setFootprint(simulateResponse.footprint!); + transaction.sign(adminKeypair, Network.FUTURENET); + + // check transaction xdr encoding and decoding back and forth + String transactionEnvelopeXdr = transaction.toEnvelopeXdrBase64(); + assert(transactionEnvelopeXdr == + AbstractTransaction.fromEnvelopeXdrString(transactionEnvelopeXdr) + .toEnvelopeXdrBase64()); + + // send the transaction + SendTransactionResponse sendResponse = + await sorobanServer.sendTransaction(transaction); + assert(sendResponse.error == null); + assert(sendResponse.resultError == null); + + GetTransactionStatusResponse statusResponse = + await pollStatus(sendResponse.transactionId!); + String status = statusResponse.status!; + assert(status == SorobanServer.TRANSACTION_STATUS_SUCCESS); + + assert(statusResponse.getResultValue()?.getI128() != null); + XdrInt128Parts parts = statusResponse.getResultValue()!.getI128()!; + return parts.lo.uint64; + } + + group('all tests', () { + + test('test install contracts', () async { + + tokenAContractWasmId = await installContract(tokenContractPath); + tokenBContractWasmId = await installContract(tokenContractPath); + swapContractWasmId = await installContract(swapContractPath); + }); + + test('test create contracts', () async { + tokenAContractId = await createContract(tokenAContractWasmId!); + print("Token A Contract ID: " + tokenAContractId!); + tokenBContractId = await createContract(tokenBContractWasmId!); + print("Token B Contract ID: " + tokenBContractId!); + swapContractId = await createContract(swapContractWasmId!); + print("SWAP Contract ID: " + swapContractId!); + }); + + test('test create tokens', () async { + await createToken(tokenAContractId!, "TokenA", "TokenA"); + await createToken(tokenBContractId!, "TokenB", "TokenB"); + }); + + test('test mint tokens', () async { + await mint(tokenAContractId!, aliceId, 10000000000000); + await mint(tokenBContractId!, bobId, 10000000000000); + int aliceTokenABalance = await balance(tokenAContractId!, aliceId); + assert(aliceTokenABalance == 10000000000000); + int bobTokenBBalance = await balance(tokenBContractId!, bobId); + assert(bobTokenBBalance == 10000000000000); + }); + + test('test atomic swap', () async { + // See https://soroban.stellar.org/docs/how-to-guides/atomic-swap + // See https://soroban.stellar.org/docs/learn/authorization + + await Future.delayed(const Duration(seconds: 10), () {}); + + KeyPair swapSubmitterKp = adminKeypair; + String swapSubmitterAccountId = swapSubmitterKp.accountId; + + KeyPair aliceKp = aliceKeypair; + String aliceAccountId = aliceKp.accountId; + + KeyPair bobKp = bobKeypair; + String bobAccountId = bobKp.accountId; + + String atomicSwapContractId = swapContractId!; + String tokenACId = tokenAContractId!; + String tokenBCId = tokenBContractId!; + + Address addressAlice = Address.forAccountId(aliceAccountId); + Address addressBob = Address.forAccountId(bobAccountId); + Address addressSwapContract = Address.forContractId(atomicSwapContractId); + + XdrSCVal tokenABytes = XdrSCVal.forObject( + XdrSCObject.forBytes(Util.hexToBytes(tokenACId))); + XdrSCVal tokenBBytes = XdrSCVal.forObject( + XdrSCObject.forBytes(Util.hexToBytes(tokenBCId))); + + XdrSCVal amountA = XdrSCVal.forObject( + XdrSCObject.forI128(XdrInt128Parts.forLoHi(1000, 0))); + XdrSCVal minBForA = XdrSCVal.forObject( + XdrSCObject.forI128(XdrInt128Parts.forLoHi(4500, 0))); + + XdrSCVal amountB = XdrSCVal.forObject( + XdrSCObject.forI128(XdrInt128Parts.forLoHi(5000, 0))); + XdrSCVal minAForB = XdrSCVal.forObject( + XdrSCObject.forI128(XdrInt128Parts.forLoHi(950, 0))); + + String swapFuntionName = "swap"; + String incrAllowFunctionName = "incr_allow"; + + List aliceSubAuthArgs = [ + addressAlice.toXdrSCVal(), + addressSwapContract.toXdrSCVal(), + amountA + ]; + AuthorizedInvocation aliceSubAuthInvocation = AuthorizedInvocation( + tokenACId, incrAllowFunctionName, + args: aliceSubAuthArgs); + List aliceRootAuthArgs = [ + tokenABytes, + tokenBBytes, + amountA, + minBForA + ]; + AuthorizedInvocation aliceRootInvocation = AuthorizedInvocation( + atomicSwapContractId, swapFuntionName, + args: aliceRootAuthArgs, subInvocations: [aliceSubAuthInvocation]); + + List bobSubAuthArgs = [ + addressBob.toXdrSCVal(), + addressSwapContract.toXdrSCVal(), + amountB + ]; + AuthorizedInvocation bobSubAuthInvocation = AuthorizedInvocation( + tokenBCId, incrAllowFunctionName, + args: bobSubAuthArgs); + List bobRootAuthArgs = [ + tokenBBytes, + tokenABytes, + amountB, + minAForB + ]; + AuthorizedInvocation bobRootInvocation = AuthorizedInvocation( + atomicSwapContractId, swapFuntionName, + args: bobRootAuthArgs, subInvocations: [bobSubAuthInvocation]); + + int aliceNonce = + await sorobanServer.getNonce(aliceAccountId, atomicSwapContractId); + ContractAuth aliceContractAuth = ContractAuth(aliceRootInvocation, + address: addressAlice, nonce: aliceNonce); + aliceContractAuth.sign(aliceKp, Network.FUTURENET); + + int bobNonce = + await sorobanServer.getNonce(bobAccountId, atomicSwapContractId); + ContractAuth bobContractAuth = + ContractAuth(bobRootInvocation, address: addressBob, nonce: bobNonce); + bobContractAuth.sign(bobKp, Network.FUTURENET); + + List invokeArgs = [ + addressAlice.toXdrSCVal(), + addressBob.toXdrSCVal(), + tokenABytes, + tokenBBytes, + amountA, + minBForA, + amountB, + minAForB + ]; + + // load submitter account for sequence number + GetAccountResponse swapSubmitter = + await sorobanServer.getAccount(swapSubmitterAccountId); + assert(!swapSubmitter.isErrorResponse); + + InvokeHostFunctionOperation operation = + InvokeHostFuncOpBuilder.forInvokingContract( + atomicSwapContractId, swapFuntionName, + functionArguments: invokeArgs, + contractAuth: [aliceContractAuth, bobContractAuth]).build(); + + Transaction transaction = + new TransactionBuilder(swapSubmitter).addOperation(operation).build(); + + // simulate first to get footprint + SimulateTransactionResponse simulateResponse = + await sorobanServer.simulateTransaction(transaction); + assert(simulateResponse.error == null); + assert(simulateResponse.resultError == null); + assert(simulateResponse.footprint != null); + + // set footprint and sign + transaction.setFootprint(simulateResponse.footprint!); + transaction.sign(swapSubmitterKp, Network.FUTURENET); + + // check transaction xdr encoding and decoding back and forth + String transactionEnvelopeXdr = transaction.toEnvelopeXdrBase64(); + assert(transactionEnvelopeXdr == + AbstractTransaction.fromEnvelopeXdrString(transactionEnvelopeXdr) + .toEnvelopeXdrBase64()); + + // send the transaction + SendTransactionResponse sendResponse = + await sorobanServer.sendTransaction(transaction); + assert(sendResponse.error == null); + assert(sendResponse.resultError == null); + + GetTransactionStatusResponse statusResponse = + await pollStatus(sendResponse.transactionId!); + String status = statusResponse.status!; + assert(status == SorobanServer.TRANSACTION_STATUS_SUCCESS); + print("Result " + statusResponse.results![0].xdr); + }); + }); +} diff --git a/test/soroban_test_auth.dart b/test/soroban_test_auth.dart index 2aba1ea..0065b87 100644 --- a/test/soroban_test_auth.dart +++ b/test/soroban_test_auth.dart @@ -414,157 +414,5 @@ void main() { assert(false); } }); - - test('test atomic swap', () async { - // See https://soroban.stellar.org/docs/how-to-guides/atomic-swap - // See https://soroban.stellar.org/docs/learn/authorization - // See https://github.com/StellarCN/py-stellar-base/blob/soroban/examples/soroban_auth_atomic_swap.py - - KeyPair swapSubmitterKp = KeyPair.fromSecretSeed( - "SBPTTA3D3QYQ6E2GSACAZDUFH2UILBNG3EBJCK3NNP7BE4O757KGZUGA"); - String swapSubmitterAccountId = swapSubmitterKp - .accountId; // GAERW3OYAVYMZMPMVKHSCDS4ORFPLT5Z3YXA4VM3BVYEA2W7CG3V6YYB - - KeyPair aliceKp = KeyPair.fromSecretSeed( - "SAAPYAPTTRZMCUZFPG3G66V4ZMHTK4TWA6NS7U4F7Z3IMUD52EK4DDEV"); - String aliceAccountId = aliceKp - .accountId; // GDAT5HWTGIU4TSSZ4752OUC4SABDLTLZFRPZUJ3D6LKBNEPA7V2CIG54 - - KeyPair bobKp = KeyPair.fromSecretSeed( - "SAEZSI6DY7AXJFIYA4PM6SIBNEYYXIEM2MSOTHFGKHDW32MBQ7KVO6EN"); - String bobAccountId = bobKp - .accountId; // GBMLPRFCZDZJPKUPHUSHCKA737GOZL7ERZLGGMJ6YGHBFJZ6ZKMKCZTM - - String atomicSwapContractId = - "828e7031194ec4fb9461d8283b448d3eaf5e36357cf465d8db6021ded6eff05c"; - String nativeTokenContractId = - "d93f5c7bb0ebc4a9c8f727c5cebc4e41194d38257e1d0d910356b43bfc528813"; - String catTokenContractId = - "8dc97b166bd98c755b0e881ee9bd6d0b45e797ec73671f30e026f14a0f1cce67"; - - Address addressAlice = Address.forAccountId(aliceAccountId); - Address addressBob = Address.forAccountId(bobAccountId); - Address addressSwapContract = Address.forContractId(atomicSwapContractId); - - XdrSCVal tokenABytes = XdrSCVal.forObject( - XdrSCObject.forBytes(Util.hexToBytes(nativeTokenContractId))); - XdrSCVal tokenBBytes = XdrSCVal.forObject( - XdrSCObject.forBytes(Util.hexToBytes(catTokenContractId))); - - XdrSCVal amountA = XdrSCVal.forObject( - XdrSCObject.forI128(XdrInt128Parts.forLoHi(1000, 0))); - XdrSCVal minBForA = XdrSCVal.forObject( - XdrSCObject.forI128(XdrInt128Parts.forLoHi(4500, 0))); - - XdrSCVal amountB = XdrSCVal.forObject( - XdrSCObject.forI128(XdrInt128Parts.forLoHi(5000, 0))); - XdrSCVal minAForB = XdrSCVal.forObject( - XdrSCObject.forI128(XdrInt128Parts.forLoHi(950, 0))); - - String swapFuntionName = "swap"; - String incrAllowFunctionName = "incr_allow"; - - List aliceSubAuthArgs = [ - addressAlice.toXdrSCVal(), - addressSwapContract.toXdrSCVal(), - amountA - ]; - AuthorizedInvocation aliceSubAuthInvocation = AuthorizedInvocation( - nativeTokenContractId, incrAllowFunctionName, - args: aliceSubAuthArgs); - List aliceRootAuthArgs = [ - tokenABytes, - tokenBBytes, - amountA, - minBForA - ]; - AuthorizedInvocation aliceRootInvocation = AuthorizedInvocation( - atomicSwapContractId, swapFuntionName, - args: aliceRootAuthArgs, subInvocations: [aliceSubAuthInvocation]); - - List bobSubAuthArgs = [ - addressBob.toXdrSCVal(), - addressSwapContract.toXdrSCVal(), - amountB - ]; - AuthorizedInvocation bobSubAuthInvocation = AuthorizedInvocation( - catTokenContractId, incrAllowFunctionName, - args: bobSubAuthArgs); - List bobRootAuthArgs = [ - tokenBBytes, - tokenABytes, - amountB, - minAForB - ]; - AuthorizedInvocation bobRootInvocation = AuthorizedInvocation( - atomicSwapContractId, swapFuntionName, - args: bobRootAuthArgs, subInvocations: [bobSubAuthInvocation]); - - int aliceNonce = - await sorobanServer.getNonce(aliceAccountId, atomicSwapContractId); - ContractAuth aliceContractAuth = ContractAuth(aliceRootInvocation, - address: addressAlice, nonce: aliceNonce); - aliceContractAuth.sign(aliceKp, Network.FUTURENET); - - int bobNonce = - await sorobanServer.getNonce(bobAccountId, atomicSwapContractId); - ContractAuth bobContractAuth = - ContractAuth(bobRootInvocation, address: addressBob, nonce: bobNonce); - bobContractAuth.sign(bobKp, Network.FUTURENET); - - List invokeArgs = [ - addressAlice.toXdrSCVal(), - addressBob.toXdrSCVal(), - tokenABytes, - tokenBBytes, - amountA, - minBForA, - amountB, - minAForB - ]; - - // load submitter account for sequence number - GetAccountResponse swapSubmitter = - await sorobanServer.getAccount(swapSubmitterAccountId); - assert(!swapSubmitter.isErrorResponse); - - InvokeHostFunctionOperation operation = - InvokeHostFuncOpBuilder.forInvokingContract( - atomicSwapContractId, swapFuntionName, - functionArguments: invokeArgs, - contractAuth: [aliceContractAuth, bobContractAuth]).build(); - - Transaction transaction = - new TransactionBuilder(swapSubmitter).addOperation(operation).build(); - - // simulate first to get footprint - SimulateTransactionResponse simulateResponse = - await sorobanServer.simulateTransaction(transaction); - assert(simulateResponse.error == null); - assert(simulateResponse.resultError == null); - assert(simulateResponse.footprint != null); - - // set footprint and sign - transaction.setFootprint(simulateResponse.footprint!); - transaction.sign(swapSubmitterKp, Network.FUTURENET); - - // check transaction xdr encoding and decoding back and forth - String transactionEnvelopeXdr = transaction.toEnvelopeXdrBase64(); - assert(transactionEnvelopeXdr == - AbstractTransaction.fromEnvelopeXdrString(transactionEnvelopeXdr) - .toEnvelopeXdrBase64()); - - // send the transaction - SendTransactionResponse sendResponse = - await sorobanServer.sendTransaction(transaction); - assert(sendResponse.error == null); - assert(sendResponse.resultError == null); - - GetTransactionStatusResponse statusResponse = - await pollStatus(sendResponse.transactionId!); - String status = statusResponse.status!; - assert(status == SorobanServer.TRANSACTION_STATUS_SUCCESS); - print("Result " + statusResponse.results![0].xdr); - }); }); } diff --git a/test/wasm/atomic_swap.wasm b/test/wasm/atomic_swap.wasm new file mode 100644 index 0000000000000000000000000000000000000000..5590c5b58a43a5a8a81b938c54362f18296863e7 GIT binary patch literal 1497 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 literal 0 HcmV?d00001