Skip to content

Commit

Permalink
Merge pull request #330 from multiversx/sc-intents-factory
Browse files Browse the repository at this point in the history
Integrate the SC Intents Factory in the SmartContract Class
  • Loading branch information
popenta authored Sep 25, 2023
2 parents 799f9f3 + 74b0478 commit 17e4312
Show file tree
Hide file tree
Showing 7 changed files with 192 additions and 60 deletions.
17 changes: 17 additions & 0 deletions src/smartcontracts/codeMetadata.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { assert } from "chai";
import { CodeMetadata } from "./codeMetadata";

describe("test code metadata", function () {
it("should test code metadata from bytes", () => {
const bytes = new Uint8Array([1, 0]);
const codeMetadata = CodeMetadata.fromBytes(bytes);

assert.equal(codeMetadata.toString(), "0100");
assert.deepEqual(codeMetadata.toJSON(), {
upgradeable: true,
readable: false,
payable: false,
payableBySc: false
});
});
});
19 changes: 18 additions & 1 deletion src/smartcontracts/codeMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export class CodeMetadata {
private readable: boolean;
private payable: boolean;
private payableBySc: boolean;
private static readonly codeMetadataLength = 2;

/**
* Creates a metadata object. By default, set the `upgradeable` attribute, and uset all others.
Expand All @@ -22,6 +23,22 @@ export class CodeMetadata {
this.payableBySc = payableBySc
}

static fromBytes(bytes: Uint8Array): CodeMetadata {
if (bytes.length !== this.codeMetadataLength) {
return new CodeMetadata();
}

const byteZero = bytes[0];
const byteOne = bytes[1];

const upgradeable = (byteZero & ByteZero.Upgradeable) !== 0;
const readable = (byteZero & ByteZero.Readable) !== 0;
const payable = (byteOne & ByteOne.Payable) !== 0;
const payableBySc = (byteOne & ByteOne.PayableBySc) !== 0;

return new CodeMetadata(upgradeable, readable, payable, payableBySc);
}

/**
* Adjust the metadata (the `upgradeable` attribute), when preparing the deployment transaction.
*/
Expand Down Expand Up @@ -49,7 +66,7 @@ export class CodeMetadata {
togglePayableBySc(value: boolean) {
this.payableBySc = value;
}

/**
* Converts the metadata to the protocol-friendly representation.
*/
Expand Down
6 changes: 3 additions & 3 deletions src/smartcontracts/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export interface ISmartContract {
export interface DeployArguments {
code: ICode;
codeMetadata?: ICodeMetadata;
initArguments?: TypedValue[];
initArguments?: any[];
value?: ITransactionValue;
gasLimit: IGasLimit;
gasPrice?: IGasPrice;
Expand All @@ -42,7 +42,7 @@ export interface DeployArguments {
export interface UpgradeArguments {
code: ICode;
codeMetadata?: ICodeMetadata;
initArguments?: TypedValue[];
initArguments?: any[];
value?: ITransactionValue;
gasLimit: IGasLimit;
gasPrice?: IGasPrice;
Expand All @@ -52,7 +52,7 @@ export interface UpgradeArguments {

export interface CallArguments {
func: IContractFunction;
args?: TypedValue[];
args?: any[];
value?: ITransactionValue;
gasLimit: IGasLimit;
receiver?: IAddress;
Expand Down
39 changes: 39 additions & 0 deletions src/smartcontracts/smartContract.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,43 @@ describe("test contract", () => {
assert.isTrue((await provider.getTransactionStatus(hashOne)).isExecuted());
assert.isTrue((await provider.getTransactionStatus(hashTwo)).isExecuted());
});

it("should upgrade", async () => {
setupUnitTestWatcherTimeouts();
let watcher = new TransactionWatcher(provider);

let contract = new SmartContract();
contract.setAddress(Address.fromBech32("erd1qqqqqqqqqqqqqpgq3ytm9m8dpeud35v3us20vsafp77smqghd8ss4jtm0q"))

let deployTransaction = contract.upgrade({
code: Code.fromBuffer(Buffer.from([1, 2, 3, 4])),
gasLimit: 1000000,
chainID: chainID,
caller: alice.address
});

provider.mockUpdateAccount(alice.address, account => {
account.nonce = 42;
});

await alice.sync(provider);
deployTransaction.setNonce(alice.account.nonce);

assert.equal(deployTransaction.getData().valueOf().toString(), "upgradeContract@01020304@0100");
assert.equal(deployTransaction.getGasLimit().valueOf(), 1000000);
assert.equal(deployTransaction.getNonce().valueOf(), 42);

// Sign the transaction
alice.signer.sign(deployTransaction);

// Now let's broadcast the deploy transaction, and wait for its execution.
let hash = await provider.sendTransaction(deployTransaction);

await Promise.all([
provider.mockTransactionTimeline(deployTransaction, [new Wait(40), new TransactionStatus("pending"), new Wait(40), new TransactionStatus("executed"), new MarkCompleted()]),
watcher.awaitCompleted(deployTransaction)
]);

assert.isTrue((await provider.getTransactionStatus(hash)).isExecuted());
});
});
132 changes: 92 additions & 40 deletions src/smartcontracts/smartContract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,19 @@ import { bigIntToBuffer } from "./codec/utils";
import { CodeMetadata } from "./codeMetadata";
import { ContractFunction } from "./function";
import { Interaction } from "./interaction";
import { CallArguments, DeployArguments, ISmartContract, QueryArguments, UpgradeArguments } from "./interface";
import { CallArguments, DeployArguments, ICodeMetadata, ISmartContract, QueryArguments, UpgradeArguments } from "./interface";
import { NativeSerializer } from "./nativeSerializer";
import { Query } from "./query";
import { ArwenVirtualMachine, ContractCallPayloadBuilder, ContractDeployPayloadBuilder, ContractUpgradePayloadBuilder } from "./transactionPayloadBuilders";
import { ArwenVirtualMachine, ContractCallPayloadBuilder, ContractUpgradePayloadBuilder } from "./transactionPayloadBuilders";
import { EndpointDefinition, TypedValue } from "./typesystem";
import { SmartContractTransactionIntentsFactory } from "../transactionIntentsFactories/smartContractTransactionIntentsFactory";
import { TransactionIntentsFactoryConfig } from "../transactionIntentsFactories/transactionIntentsFactoryConfig";
import { TransactionPayload } from "../transactionPayload";
const createKeccakHash = require("keccak");

interface IAbi {
constructorDefinition: EndpointDefinition;

getEndpoints(): EndpointDefinition[];
getEndpoint(name: string | ContractFunction): EndpointDefinition;
}
Expand Down Expand Up @@ -110,27 +115,58 @@ 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()");

codeMetadata = codeMetadata || new CodeMetadata();
initArguments = initArguments || [];
value = value || 0;
const config = new TransactionIntentsFactoryConfig(chainID.valueOf());
const scIntentFactory = new SmartContractTransactionIntentsFactory({
config: config,
abi: this.abi
});

let payload = new ContractDeployPayloadBuilder()
.setCode(code)
.setCodeMetadata(codeMetadata)
.setInitArgs(initArguments)
.build();
const bytecode = Buffer.from(code.toString(), 'hex');
const metadataAsJson = this.getMetadataPropertiesAsObject(codeMetadata);

let transaction = new Transaction({
receiver: Address.Zero(),
const intent = scIntentFactory.createTransactionIntentForDeploy({
sender: deployer,
bytecode: bytecode,
gasLimit: gasLimit.valueOf(),
args: initArguments,
isUpgradeable: metadataAsJson.upgradeable,
isReadable: metadataAsJson.readable,
isPayable: metadataAsJson.payable,
isPayableBySmartContract: metadataAsJson.payableBySc
});

return new Transaction({
receiver: Address.fromBech32(intent.receiver),
sender: Address.fromBech32(intent.sender),
value: value,
gasLimit: gasLimit,
gasLimit: new BigNumber(intent.gasLimit).toNumber(),
gasPrice: gasPrice,
data: payload,
data: new TransactionPayload(Buffer.from(intent.data!)),
chainID: chainID
});
}

return transaction;
private getMetadataPropertiesAsObject(codeMetadata?: ICodeMetadata): {
upgradeable: boolean,
readable: boolean,
payable: boolean,
payableBySc: boolean
} {
let metadata: CodeMetadata;
if (codeMetadata) {
metadata = CodeMetadata.fromBytes(Buffer.from(codeMetadata.toString(), "hex"));
}
else {
metadata = new CodeMetadata();
}
const metadataAsJson = metadata.toJSON() as {
upgradeable: boolean,
readable: boolean,
payable: boolean,
payableBySc: boolean
};

return metadataAsJson;
}

/**
Expand All @@ -141,27 +177,36 @@ export class SmartContract implements ISmartContract {

this.ensureHasAddress();

codeMetadata = codeMetadata || new CodeMetadata();
initArguments = initArguments || [];
value = value || 0;
const config = new TransactionIntentsFactoryConfig(chainID.valueOf());
const scIntentFactory = new SmartContractTransactionIntentsFactory({
config: config,
abi: this.abi
});

let payload = new ContractUpgradePayloadBuilder()
.setCode(code)
.setCodeMetadata(codeMetadata)
.setInitArgs(initArguments)
.build();
const bytecode = Uint8Array.from(Buffer.from(code.toString(), 'hex'));
const metadataAsJson = this.getMetadataPropertiesAsObject(codeMetadata);

let transaction = new Transaction({
const intent = scIntentFactory.createTransactionIntentForUpgrade({
sender: caller,
receiver: this.getAddress(),
contract: this.getAddress(),
bytecode: bytecode,
gasLimit: gasLimit.valueOf(),
args: initArguments,
isUpgradeable: metadataAsJson.upgradeable,
isReadable: metadataAsJson.readable,
isPayable: metadataAsJson.payable,
isPayableBySmartContract: metadataAsJson.payableBySc
})

return new Transaction({
sender: Address.fromBech32(intent.sender),
receiver: Address.fromBech32(intent.receiver),
value: value,
gasLimit: gasLimit,
gasLimit: new BigNumber(intent.gasLimit).toNumber(),
gasPrice: gasPrice,
data: payload,
data: new TransactionPayload(Buffer.from(intent.data!)),
chainID: chainID
});

return transaction;
}

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

this.ensureHasAddress();

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

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

let payload = new ContractCallPayloadBuilder()
.setFunction(func)
.setArgs(args)
.build();
const intent = scIntentFactory.createTransactionIntentForExecute({
sender: caller,
contractAddress: receiver ? receiver : this.getAddress(),
functionName: func.toString(),
gasLimit: gasLimit.valueOf(),
args: args
})

let transaction = new Transaction({
return new Transaction({
sender: caller,
receiver: receiver ? receiver : this.getAddress(),
receiver: Address.fromBech32(intent.receiver),
value: value,
gasLimit: gasLimit,
gasLimit: new BigNumber(intent.gasLimit).toNumber(),
gasPrice: gasPrice,
data: payload,
chainID: chainID,
data: new TransactionPayload(Buffer.from(intent.data!)),
chainID: chainID
});

return transaction;
}

createQuery({ func, args, value, caller }: QueryArguments): Query {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,14 +83,14 @@ describe("test smart contract intents factory", function () {
const deployIntent = factory.createTransactionIntentForExecute({
sender: sender,
contractAddress: contract,
func: func,
functionName: func,
gasLimit: gasLimit,
args: args
});
const abiDeployIntent = abiAwareFactory.createTransactionIntentForExecute({
sender: sender,
contractAddress: contract,
func: func,
functionName: func,
gasLimit: gasLimit,
args: args
});
Expand Down
Loading

0 comments on commit 17e4312

Please sign in to comment.