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

Update transaction factories to match the sdk-specs #339

Merged
merged 7 commits into from
Oct 23, 2023
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
6 changes: 3 additions & 3 deletions src/transactionIntent.ts → src/draftTransaction.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BigNumber } from "bignumber.js";

export class TransactionIntent {
export class DraftTransaction {
CiprianDraghici marked this conversation as resolved.
Show resolved Hide resolved
public sender: string;
public receiver: string;
public gasLimit: BigNumber.Value;
Expand All @@ -17,7 +17,7 @@ export class TransactionIntent {
this.sender = options.sender;
this.receiver = options.receiver;
this.gasLimit = options.gasLimit;
this.value = options.value;
this.data = options.data;
this.value = options.value ?? 0;
this.data = options.data ?? new Uint8Array();
}
}
14 changes: 8 additions & 6 deletions src/smartcontracts/interaction.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ describe("test smart contract interactor", function () {
let interaction = new Interaction(contract, dummyFunction, []);

let transaction = interaction
.withSender(alice.address)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct.

.withNonce(7)
.withValue(TokenTransfer.egldFromAmount(1))
.withGasLimit(20000000)
Expand Down Expand Up @@ -63,6 +64,7 @@ describe("test smart contract interactor", function () {

// ESDT, single
let transaction = new Interaction(contract, dummyFunction, [])
.withSender(alice)
.withSingleESDTTransfer(TokenFoo(10))
.buildTransaction();

Expand Down Expand Up @@ -180,7 +182,7 @@ describe("test smart contract interactor", function () {
assert.isTrue(queryCode.equals(ReturnCode.Ok));

// Execute, do not wait for execution
let transaction = interaction.withNonce(0).buildTransaction();
let transaction = interaction.withSender(alice.address).withNonce(0).buildTransaction();
transaction.setSender(alice.address);
await alice.signer.sign(transaction);
await provider.sendTransaction(transaction);
Expand Down Expand Up @@ -235,15 +237,15 @@ describe("test smart contract interactor", function () {

assert.deepEqual(counterValue!.valueOf(), new BigNumber(7));

let incrementTransaction = incrementInteraction.withNonce(14).buildTransaction();
let incrementTransaction = incrementInteraction.withSender(alice.address).withNonce(14).buildTransaction();
await alice.signer.sign(incrementTransaction);
provider.mockGetTransactionWithAnyHashAsNotarizedWithOneResult("@6f6b@08");
let { bundle: { firstValue: valueAfterIncrement } } = await controller.execute(incrementInteraction, incrementTransaction);
assert.deepEqual(valueAfterIncrement!.valueOf(), new BigNumber(8));

// Decrement three times (simulate three parallel broadcasts). Wait for execution of the latter (third transaction). Return fake "5".
// Decrement #1
let decrementTransaction = decrementInteraction.withNonce(15).buildTransaction();
let decrementTransaction = decrementInteraction.withSender(alice.address).withNonce(15).buildTransaction();
await alice.signer.sign(decrementTransaction);
await provider.sendTransaction(decrementTransaction);
// Decrement #2
Expand Down Expand Up @@ -292,7 +294,7 @@ describe("test smart contract interactor", function () {
);

// start()
let startTransaction = startInteraction.withNonce(14).buildTransaction();
let startTransaction = startInteraction.withSender(alice.address).withNonce(14).buildTransaction();
await alice.signer.sign(startTransaction);
provider.mockGetTransactionWithAnyHashAsNotarizedWithOneResult("@6f6b");
let { bundle: { returnCode: startReturnCode, values: startReturnValues } } = await controller.execute(startInteraction, startTransaction);
Expand All @@ -302,7 +304,7 @@ describe("test smart contract interactor", function () {
assert.lengthOf(startReturnValues, 0);

// status() (this is a view function, but for the sake of the test, we'll execute it)
let statusTransaction = statusInteraction.withNonce(15).buildTransaction();
let statusTransaction = statusInteraction.withSender(alice.address).withNonce(15).buildTransaction();
await alice.signer.sign(statusTransaction);
provider.mockGetTransactionWithAnyHashAsNotarizedWithOneResult("@6f6b@01");
let { bundle: { returnCode: statusReturnCode, values: statusReturnValues, firstValue: statusFirstValue } } = await controller.execute(statusInteraction, statusTransaction);
Expand All @@ -313,7 +315,7 @@ describe("test smart contract interactor", function () {
assert.deepEqual(statusFirstValue!.valueOf(), { name: "Running", fields: [] });

// lotteryInfo() (this is a view function, but for the sake of the test, we'll execute it)
let getLotteryInfoTransaction = getLotteryInfoInteraction.withNonce(15).buildTransaction();
let getLotteryInfoTransaction = getLotteryInfoInteraction.withSender(alice.address).withNonce(15).buildTransaction();
await alice.signer.sign(getLotteryInfoTransaction);
provider.mockGetTransactionWithAnyHashAsNotarizedWithOneResult("@6f6b@0000000b6c75636b792d746f6b656e000000010100000000000000005fc2b9dbffffffff00000001640000000a140ec80fa7ee88000000");
let { bundle: { returnCode: infoReturnCode, values: infoReturnValues, firstValue: infoFirstValue } } = await controller.execute(getLotteryInfoInteraction, getLotteryInfoTransaction);
Expand Down
68 changes: 30 additions & 38 deletions src/smartcontracts/smartContract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ import { NativeSerializer } from "./nativeSerializer";
import { Query } from "./query";
import { ArwenVirtualMachine, ContractCallPayloadBuilder, ContractUpgradePayloadBuilder } from "./transactionPayloadBuilders";
import { EndpointDefinition, TypedValue } from "./typesystem";
import { SmartContractTransactionIntentsFactory } from "../transactionIntentsFactories/smartContractTransactionIntentsFactory";
import { TransactionIntentsFactoryConfig } from "../transactionIntentsFactories/transactionIntentsFactoryConfig";
import { SmartContractTransactionsFactory } from "../transactionsFactories/smartContractTransactionsFactory";
import { TransactionsFactoryConfig } from "../transactionsFactories/transactionsFactoryConfig";
import { TransactionPayload } from "../transactionPayload";
import { TRANSACTION_MIN_GAS_PRICE } from "../constants";
const createKeccakHash = require("keccak");

interface IAbi {
Expand Down Expand Up @@ -115,16 +116,16 @@ export class SmartContract implements ISmartContract {
deploy({ deployer, code, codeMetadata, initArguments, value, gasLimit, gasPrice, chainID }: DeployArguments): Transaction {
Compatibility.guardAddressIsSetAndNonZero(deployer, "'deployer' of SmartContract.deploy()", "pass the actual address to deploy()");

const config = new TransactionIntentsFactoryConfig(chainID.valueOf());
const scIntentFactory = new SmartContractTransactionIntentsFactory({
const config = new TransactionsFactoryConfig(chainID.valueOf());
const scDraftTransactionFactory = new SmartContractTransactionsFactory({
config: config,
abi: this.abi
});

const bytecode = Buffer.from(code.toString(), 'hex');
const metadataAsJson = this.getMetadataPropertiesAsObject(codeMetadata);

const intent = scIntentFactory.createTransactionIntentForDeploy({
const draftTx = scDraftTransactionFactory.createTransactionForDeploy({
sender: deployer,
bytecode: bytecode,
gasLimit: gasLimit.valueOf(),
Expand All @@ -135,15 +136,12 @@ export class SmartContract implements ISmartContract {
isPayableBySmartContract: metadataAsJson.payableBySc
});

return new Transaction({
receiver: Address.fromBech32(intent.receiver),
sender: Address.fromBech32(intent.sender),
value: value,
gasLimit: new BigNumber(intent.gasLimit).toNumber(),
gasPrice: gasPrice,
data: new TransactionPayload(Buffer.from(intent.data!)),
chainID: chainID
});
let transaction = Transaction.fromDraft(draftTx);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can also be const (even if we mutate its inner state).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's now const.

transaction.setChainID(chainID);
transaction.setValue(value ?? 0);
transaction.setGasPrice(gasPrice ?? TRANSACTION_MIN_GAS_PRICE)

return transaction;
}

private getMetadataPropertiesAsObject(codeMetadata?: ICodeMetadata): {
Expand Down Expand Up @@ -177,16 +175,16 @@ export class SmartContract implements ISmartContract {

this.ensureHasAddress();

const config = new TransactionIntentsFactoryConfig(chainID.valueOf());
const scIntentFactory = new SmartContractTransactionIntentsFactory({
const config = new TransactionsFactoryConfig(chainID.valueOf());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we do not need chain ID on the factory config?

const scDraftTransactionFactory = new SmartContractTransactionsFactory({
config: config,
abi: this.abi
});

const bytecode = Uint8Array.from(Buffer.from(code.toString(), 'hex'));
const metadataAsJson = this.getMetadataPropertiesAsObject(codeMetadata);

const intent = scIntentFactory.createTransactionIntentForUpgrade({
const draftTx = scDraftTransactionFactory.createTransactionForUpgrade({
sender: caller,
contract: this.getAddress(),
bytecode: bytecode,
Expand All @@ -198,15 +196,12 @@ export class SmartContract implements ISmartContract {
isPayableBySmartContract: metadataAsJson.payableBySc
})

return new Transaction({
sender: Address.fromBech32(intent.sender),
receiver: Address.fromBech32(intent.receiver),
value: value,
gasLimit: new BigNumber(intent.gasLimit).toNumber(),
gasPrice: gasPrice,
data: new TransactionPayload(Buffer.from(intent.data!)),
chainID: chainID
});
let transaction = Transaction.fromDraft(draftTx);
transaction.setChainID(chainID);
transaction.setValue(value ?? 0);
transaction.setGasPrice(gasPrice ?? TRANSACTION_MIN_GAS_PRICE)

return transaction;
}

/**
Expand All @@ -217,32 +212,29 @@ export class SmartContract implements ISmartContract {

this.ensureHasAddress();

const config = new TransactionIntentsFactoryConfig(chainID.valueOf());
const scIntentFactory = new SmartContractTransactionIntentsFactory({
const config = new TransactionsFactoryConfig(chainID.valueOf());
const scDraftTransactionFactory = new SmartContractTransactionsFactory({
config: config,
abi: this.abi
});

args = args || [];
value = value || 0;

const intent = scIntentFactory.createTransactionIntentForExecute({
const draftTx = scDraftTransactionFactory.createTransactionForExecute({
sender: caller,
contractAddress: receiver ? receiver : this.getAddress(),
functionName: func.toString(),
gasLimit: gasLimit.valueOf(),
args: args
})

return new Transaction({
sender: caller,
receiver: Address.fromBech32(intent.receiver),
value: value,
gasLimit: new BigNumber(intent.gasLimit).toNumber(),
gasPrice: gasPrice,
data: new TransactionPayload(Buffer.from(intent.data!)),
chainID: chainID
});
let transaction = Transaction.fromDraft(draftTx);
transaction.setChainID(chainID);
transaction.setValue(value);
transaction.setGasPrice(gasPrice ?? TRANSACTION_MIN_GAS_PRICE)

return transaction;
}

createQuery({ func, args, value, caller }: QueryArguments): Query {
Expand Down
19 changes: 19 additions & 0 deletions src/transaction.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { TestWallet, loadTestWallets } from "./testutils";
import { TokenTransfer } from "./tokenTransfer";
import { Transaction } from "./transaction";
import { TransactionPayload } from "./transactionPayload";
import { DraftTransaction } from "./draftTransaction";


describe("test transaction construction", async () => {
Expand All @@ -17,6 +18,24 @@ describe("test transaction construction", async () => {
wallets = await loadTestWallets();
});

it("create transaction from draft transaction", async () => {
const draftTransaction = new DraftTransaction({
sender: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th",
receiver: "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx",
gasLimit: 56000,
value: "1000000000000000000",
data: Buffer.from("test")
});

const transaction = Transaction.fromDraft(draftTransaction);
assert.deepEqual(transaction.getSender(), Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"));
assert.deepEqual(transaction.getReceiver(), Address.fromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"));
assert.equal(transaction.getGasLimit().valueOf(), 56000);
assert.equal(transaction.getValue().toString(), "1000000000000000000");
assert.equal(transaction.getData().toString(), "test");
assert.equal(transaction.getChainID().valueOf(), "");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps also add asserts for the other empty fields (e.g. nonce, gas price, signature etc.)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added new asserts.

});

it("with no data, no value", async () => {
let transaction = new Transaction({
nonce: 89,
Expand Down
15 changes: 15 additions & 0 deletions src/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { ProtoSerializer } from "./proto";
import { Signature } from "./signature";
import { TransactionPayload } from "./transactionPayload";
import { guardNotEmpty } from "./utils";
import { DraftTransaction } from "./draftTransaction";

const createTransactionHasher = require("blake2b");
const TRANSACTION_HASH_LENGTH = 32;
Expand Down Expand Up @@ -152,6 +153,20 @@ export class Transaction {
this.hash = TransactionHash.empty();
}

/**
* Creates a new Transaction object from a DraftTransaction.
*/
Comment on lines +156 to +158
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Formatting / indentation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed it

static fromDraft(draft: DraftTransaction): Transaction {
return new Transaction({
sender: Address.fromBech32(draft.sender),
receiver: Address.fromBech32(draft.receiver),
gasLimit: new BigNumber(draft.gasLimit).toNumber(),
chainID: "",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is a bit unfortunate at this moment (since chainID is required on the Transaction).

value: draft.value ?? 0,
data: draft.data ? new TransactionPayload(Buffer.from(draft.data)) : new TransactionPayload()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new TransactionPayload() accepts buffers, as well.

})
}

getNonce(): INonce {
return this.nonce;
}
Expand Down
Loading
Loading