diff --git a/package-lock.json b/package-lock.json index b74e4578..09aefbaf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@multiversx/sdk-core", - "version": "13.1.0", + "version": "13.2.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@multiversx/sdk-core", - "version": "13.1.0", + "version": "13.2.0", "license": "MIT", "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", diff --git a/package.json b/package.json index df883f36..aaa60706 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@multiversx/sdk-core", - "version": "13.1.0", + "version": "13.2.0", "description": "MultiversX SDK for JavaScript and TypeScript", "main": "out/index.js", "types": "out/index.d.js", diff --git a/src/smartcontracts/typesystem/abiRegistry.ts b/src/smartcontracts/typesystem/abiRegistry.ts index 601b65be..25162de2 100644 --- a/src/smartcontracts/typesystem/abiRegistry.ts +++ b/src/smartcontracts/typesystem/abiRegistry.ts @@ -12,6 +12,7 @@ const interfaceNamePlaceholder = "?"; export class AbiRegistry { readonly name: string; readonly constructorDefinition: EndpointDefinition; + readonly upgradeConstructorDefinition?: EndpointDefinition; readonly endpoints: EndpointDefinition[] = []; readonly customTypes: CustomType[] = []; readonly events: EventDefinition[] = []; @@ -19,12 +20,14 @@ export class AbiRegistry { private constructor(options: { name: string; constructorDefinition: EndpointDefinition; + upgradeConstructorDefinition?: EndpointDefinition; endpoints: EndpointDefinition[]; customTypes: CustomType[]; events?: EventDefinition[]; }) { this.name = options.name; this.constructorDefinition = options.constructorDefinition; + this.upgradeConstructorDefinition = options.upgradeConstructorDefinition; this.endpoints = options.endpoints; this.customTypes = options.customTypes; this.events = options.events || []; @@ -33,18 +36,25 @@ export class AbiRegistry { static create(options: { name?: string; constructor?: any; + upgradeConstructor?: any; endpoints?: any[]; types?: Record; events?: any[]; }): AbiRegistry { const name = options.name || interfaceNamePlaceholder; const constructor = options.constructor || {}; + const upgradeConstructor = options.upgradeConstructor || {}; const endpoints = options.endpoints || []; const types = options.types || {}; const events = options.events || []; // Load arbitrary input parameters into properly-defined objects (e.g. EndpointDefinition and CustomType). const constructorDefinition = EndpointDefinition.fromJSON({ name: "constructor", ...constructor }); + const upgradeConstructorDefinition = EndpointDefinition.fromJSON({ + name: "upgradeConstructor", + ...upgradeConstructor, + }); + const endpointDefinitions = endpoints.map((item) => EndpointDefinition.fromJSON(item)); const customTypes: CustomType[] = []; @@ -65,6 +75,7 @@ export class AbiRegistry { const registry = new AbiRegistry({ name: name, constructorDefinition: constructorDefinition, + upgradeConstructorDefinition: upgradeConstructorDefinition, endpoints: endpointDefinitions, customTypes: customTypes, events: eventDefinitions, @@ -139,8 +150,11 @@ export class AbiRegistry { throw new errors.ErrTypingSystem("Did not re-map all custom types"); } - // Let's remap the constructor: + // Let's remap the constructor(s): const newConstructor = mapEndpoint(this.constructorDefinition, mapper); + const newUpgradeConstructor = this.upgradeConstructorDefinition + ? mapEndpoint(this.upgradeConstructorDefinition, mapper) + : undefined; // Then, remap types of all endpoint parameters. // The mapper learned all necessary types in the previous step. @@ -156,6 +170,7 @@ export class AbiRegistry { const newRegistry = new AbiRegistry({ name: this.name, constructorDefinition: newConstructor, + upgradeConstructorDefinition: newUpgradeConstructor, endpoints: newEndpoints, customTypes: newCustomTypes, events: newEvents, diff --git a/src/testdata/adder.abi.json b/src/testdata/adder.abi.json index 7aba85f5..fd809ce9 100644 --- a/src/testdata/adder.abi.json +++ b/src/testdata/adder.abi.json @@ -1,20 +1,20 @@ { "buildInfo": { "rustc": { - "version": "1.71.0-nightly", - "commitHash": "a2b1646c597329d0a25efa3889b66650f65de1de", - "commitDate": "2023-05-25", + "version": "1.76.0-nightly", + "commitHash": "d86d65bbc19b928387f68427fcc3a0da498d8a19", + "commitDate": "2023-12-10", "channel": "Nightly", - "short": "rustc 1.71.0-nightly (a2b1646c5 2023-05-25)" + "short": "rustc 1.76.0-nightly (d86d65bbc 2023-12-10)" }, "contractCrate": { "name": "adder", "version": "0.0.0", - "gitVersion": "v0.45.2.1-reproducible-169-g37d970c" + "gitVersion": "v0.50.1-3-gbed74682a" }, "framework": { "name": "multiversx-sc", - "version": "0.47.2" + "version": "0.50.1" } }, "docs": [ @@ -31,6 +31,15 @@ ], "outputs": [] }, + "upgradeConstructor": { + "inputs": [ + { + "name": "initial_value", + "type": "BigUint" + } + ], + "outputs": [] + }, "endpoints": [ { "name": "getSum", @@ -43,25 +52,9 @@ ] }, { - "name": "upgrade", - "mutability": "mutable", - "inputs": [ - { - "name": "new_value", - "type": "BigUint" - } - ], - "outputs": [] - }, - { - "docs": [ - "Add desired amount to the storage variable." - ], + "docs": ["Add desired amount to the storage variable."], "name": "add", "mutability": "mutable", - "payableInTokens": [ - "*" - ], "inputs": [ { "name": "value", diff --git a/src/transactionsFactories/smartContractTransactionsFactory.spec.ts b/src/transactionsFactories/smartContractTransactionsFactory.spec.ts index 67887915..c10efb99 100644 --- a/src/transactionsFactories/smartContractTransactionsFactory.spec.ts +++ b/src/transactionsFactories/smartContractTransactionsFactory.spec.ts @@ -348,25 +348,104 @@ describe("test smart contract transactions factory", function () { assert.deepEqual(transaction, transactionAbiAware); }); - it("should create 'Transaction' for upgrade, when ABI is available, but it doesn't contain a definition for 'upgrade'", async function () { - const abi = await loadAbiRegistry("src/testdata/adder.abi.json"); - // Remove all endpoints (for the sake of the test). - abi.endpoints.length = 0; + it("should create 'Transaction' for upgrade, when ABI is available (with fallbacks)", async function () { + const abi = AbiRegistry.create({ + upgradeConstructor: { + inputs: [ + { + type: "u32", + }, + { + type: "u32", + }, + { + type: "u32", + }, + ], + }, + endpoints: [ + { + name: "upgrade", + inputs: [ + { + type: "u32", + }, + { + type: "u32", + }, + ], + }, + ], + constructor: { + inputs: [ + { + type: "u32", + }, + ], + }, + }); const factory = new SmartContractTransactionsFactory({ config: config, abi: abi, }); - const transaction = factory.createTransactionForUpgrade({ - sender: Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"), - contract: Address.fromBech32("erd1qqqqqqqqqqqqqpgqhy6nl6zq07rnzry8uyh6rtyq0uzgtk3e69fqgtz9l4"), - bytecode: adderByteCode.valueOf(), - gasLimit: 6000000n, - arguments: [new U32Value(7)], + const bytecode = Buffer.from("abba", "hex"); + const sender = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + const receiver = Address.fromBech32("erd1qqqqqqqqqqqqqpgqhy6nl6zq07rnzry8uyh6rtyq0uzgtk3e69fqgtz9l4"); + const gasLimit = 6000000n; + + // By default, use the upgrade constructor. + const tx1 = factory.createTransactionForUpgrade({ + sender: sender, + contract: receiver, + bytecode: bytecode, + gasLimit: gasLimit, + arguments: [42, 42, 42], }); - assert.equal(Buffer.from(transaction.data!).toString(), `upgradeContract@${adderByteCode}@0504@07`); + assert.equal(Buffer.from(tx1.data!).toString(), `upgradeContract@abba@0504@2a@2a@2a`); + + // Fallback to the "upgrade" endpoint. + (abi).upgradeConstructorDefinition = undefined; + + const tx2 = factory.createTransactionForUpgrade({ + sender: sender, + contract: receiver, + bytecode: bytecode, + gasLimit: gasLimit, + arguments: [42, 42], + }); + + assert.equal(Buffer.from(tx2.data!).toString(), `upgradeContract@abba@0504@2a@2a`); + + // Fallback to the constructor. + (abi).endpoints.length = 0; + + const tx3 = factory.createTransactionForUpgrade({ + sender: sender, + contract: receiver, + bytecode: bytecode, + gasLimit: gasLimit, + arguments: [42], + }); + + assert.equal(Buffer.from(tx3.data!).toString(), `upgradeContract@abba@0504@2a`); + + // No fallbacks. + (abi).constructorDefinition = undefined; + + assert.throws( + () => + factory.createTransactionForUpgrade({ + sender: sender, + contract: receiver, + bytecode: bytecode, + gasLimit: gasLimit, + arguments: [42], + }), + "Can't convert args to TypedValues", + ); }); it("should create 'Transaction' for claiming developer rewards", async function () { diff --git a/src/transactionsFactories/smartContractTransactionsFactory.ts b/src/transactionsFactories/smartContractTransactionsFactory.ts index e6982c7d..f2bd623d 100644 --- a/src/transactionsFactories/smartContractTransactionsFactory.ts +++ b/src/transactionsFactories/smartContractTransactionsFactory.ts @@ -21,6 +21,7 @@ interface IConfig { interface IAbi { constructorDefinition: EndpointDefinition; + upgradeConstructorDefinition?: EndpointDefinition; getEndpoint(name: string | ContractFunction): EndpointDefinition; } @@ -171,6 +172,10 @@ export class SmartContractTransactionsFactory { return undefined; } + if (this.abi.upgradeConstructorDefinition) { + return this.abi.upgradeConstructorDefinition; + } + try { return this.abi.getEndpoint("upgrade"); } catch (error) {