From f9acda80723365c2c06644332f1193d35d4a50d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Sun, 12 Feb 2023 23:35:49 +0200 Subject: [PATCH 01/11] Add failing tests (from #237, #224). --- .../typesystem/abiRegistry.spec.ts | 24 +- ...n => custom-types-out-of-order-A.abi.json} | 0 .../custom-types-out-of-order-B.abi.json | 66 ++ .../custom-types-out-of-order-C.abi.json | 254 +++++ .../custom-types-out-of-order-D.abi.json | 923 ++++++++++++++++++ 5 files changed, 1264 insertions(+), 3 deletions(-) rename src/testdata/{custom-types-out-of-order.abi.json => custom-types-out-of-order-A.abi.json} (100%) create mode 100644 src/testdata/custom-types-out-of-order-B.abi.json create mode 100644 src/testdata/custom-types-out-of-order-C.abi.json create mode 100644 src/testdata/custom-types-out-of-order-D.abi.json diff --git a/src/smartcontracts/typesystem/abiRegistry.spec.ts b/src/smartcontracts/typesystem/abiRegistry.spec.ts index 40588f49..47cc4fdf 100644 --- a/src/smartcontracts/typesystem/abiRegistry.spec.ts +++ b/src/smartcontracts/typesystem/abiRegistry.spec.ts @@ -7,7 +7,7 @@ import { BytesType } from "./bytes"; import { EnumType } from "./enum"; import { ListType, OptionType } from "./generic"; import { ArrayVecType } from "./genericArray"; -import { BigUIntType, I64Type, U32Type, U64Type, U8Type } from "./numerical"; +import { BigUIntType, I64Type, U32Type, U64Type } from "./numerical"; import { StructType } from "./struct"; import { TokenIdentifierType } from "./tokenIdentifier"; @@ -98,8 +98,8 @@ describe("test abi registry", () => { assert.equal(dummyType.getFieldDefinition("raw")!.type.getClassName(), ArrayVecType.ClassName); }); - it("should load ABI when custom types are out of order", async () => { - const registry = await loadAbiRegistry("src/testdata/custom-types-out-of-order.abi.json"); + it("should load ABI when custom types are out of order (A)", async () => { + const registry = await loadAbiRegistry("src/testdata/custom-types-out-of-order-A.abi.json"); assert.deepEqual(registry.getStruct("EsdtTokenPayment").getNamesOfDependencies(), ["EsdtTokenType", "TokenIdentifier", "u64", "BigUint"]); assert.deepEqual(registry.getEnum("EsdtTokenType").getNamesOfDependencies(), []); @@ -107,4 +107,22 @@ describe("test abi registry", () => { assert.deepEqual(registry.getStruct("TypeB").getNamesOfDependencies(), ["TypeC", "u64"]); assert.deepEqual(registry.getStruct("TypeC").getNamesOfDependencies(), ["u64"]); }); + + it("should load ABI when custom types are out of order (B)", async () => { + const registry = await loadAbiRegistry("src/testdata/custom-types-out-of-order-B.abi.json"); + + assert.deepEqual(registry.getStruct("EsdtTokenPayment").getNamesOfDependencies(), ["EsdtTokenType", "TokenIdentifier", "u64", "BigUint"]); + assert.deepEqual(registry.getEnum("EsdtTokenType").getNamesOfDependencies(), []); + assert.deepEqual(registry.getStruct("TypeA").getNamesOfDependencies(), ["TypeB", "TypeC", "u64"]); + assert.deepEqual(registry.getStruct("TypeB").getNamesOfDependencies(), ["TypeC", "u64"]); + assert.deepEqual(registry.getStruct("TypeC").getNamesOfDependencies(), ["u64"]); + }); + + it("should load ABI when custom types are out of order (C, D)", async () => { + const c = await loadAbiRegistry("src/testdata/custom-types-out-of-order-C.abi.json"); + assert.lengthOf(c.customTypes, 5); + + const d = await loadAbiRegistry("src/testdata/custom-types-out-of-order-D.abi.json"); + assert.lengthOf(d.customTypes, 12); + }); }); diff --git a/src/testdata/custom-types-out-of-order.abi.json b/src/testdata/custom-types-out-of-order-A.abi.json similarity index 100% rename from src/testdata/custom-types-out-of-order.abi.json rename to src/testdata/custom-types-out-of-order-A.abi.json diff --git a/src/testdata/custom-types-out-of-order-B.abi.json b/src/testdata/custom-types-out-of-order-B.abi.json new file mode 100644 index 00000000..899cc0ae --- /dev/null +++ b/src/testdata/custom-types-out-of-order-B.abi.json @@ -0,0 +1,66 @@ +{ + "name": "Sample", + "types": { + "EsdtTokenPayment": { + "type": "struct", + "fields": [ + { + "name": "token_type", + "type": "EsdtTokenType" + }, + { + "name": "token_identifier", + "type": "TokenIdentifier" + }, + { + "name": "token_nonce", + "type": "u64" + }, + { + "name": "amount", + "type": "BigUint" + } + ] + }, + "TypeC": { + "type": "struct", + "fields": [ + { + "name": "foobar", + "type": "u64" + } + ] + }, + "EsdtTokenType": { + "type": "enum", + "variants": [ + { + "name": "Fungible", + "discriminant": 0 + }, + { + "name": "NonFungible", + "discriminant": 1 + } + ] + }, + "TypeB": { + "type": "struct", + "fields": [ + { + "name": "c", + "type": "TypeC" + } + ] + }, + "TypeA": { + "type": "struct", + "fields": [ + { + "name": "b", + "type": "TypeB" + } + ] + } + } +} diff --git a/src/testdata/custom-types-out-of-order-C.abi.json b/src/testdata/custom-types-out-of-order-C.abi.json new file mode 100644 index 00000000..1a47ec19 --- /dev/null +++ b/src/testdata/custom-types-out-of-order-C.abi.json @@ -0,0 +1,254 @@ +{ + "name": "Issue_237_Part_1", + "constructor": { + "inputs": [], + "outputs": [] + }, + "endpoints": [ + { + "name": "getById", + "mutability": "readonly", + "inputs": [ + { + "name": "id", + "type": "BigUint" + } + ], + "outputs": [ + { + "type": "LoanInfo" + } + ] + }, + { + "name": "getList", + "mutability": "readonly", + "inputs": [ + { + "name": "page", + "type": "u64" + }, + { + "name": "size", + "type": "u16" + } + ], + "outputs": [ + { + "type": "variadic", + "multi_result": true + } + ] + }, + { + "name": "create", + "mutability": "mutable", + "inputs": [ + { + "name": "data", + "type": "LoanCreateOptions" + } + ], + "outputs": [ + { + "type": "LoanInfo" + } + ] + }, + { + "name": "createByArgs", + "mutability": "mutable", + "inputs": [ + { + "name": "amount", + "type": "BigUint" + }, + { + "name": "loaner_address", + "type": "Address" + }, + { + "name": "token", + "type": "EgldOrEsdtTokenIdentifier" + }, + { + "name": "status", + "type": "Status" + }, + { + "name": "note", + "type": "bytes" + } + ], + "outputs": [ + { + "type": "LoanInfo" + } + ] + }, + { + "name": "updateStatus", + "mutability": "mutable", + "inputs": [ + { + "name": "id", + "type": "BigUint" + }, + { + "name": "status", + "type": "Status" + } + ], + "outputs": [ + { + "type": "Status" + } + ] + }, + { + "name": "setArrayOfStruct", + "mutability": "mutable", + "inputs": [ + { + "name": "list", + "type": "variadic", + "multi_arg": true + } + ], + "outputs": [] + }, + { + "name": "setArrayOfAddresses", + "mutability": "mutable", + "inputs": [ + { + "name": "list", + "type": "variadic
", + "multi_arg": true + } + ], + "outputs": [] + }, + { + "name": "setArrayOfBigInt", + "mutability": "mutable", + "inputs": [ + { + "name": "list", + "type": "variadic", + "multi_arg": true + } + ], + "outputs": [] + } + ], + "events": [], + "hasCallback": false, + "types": { + "Item": { + "type": "struct", + "fields": [ + { + "name": "amount", + "type": "BigUint" + }, + { + "name": "address", + "type": "Address" + } + ] + }, + "LoanCreateOptions": { + "type": "struct", + "fields": [ + { + "name": "amount", + "type": "BigUint" + }, + { + "name": "loaner_address", + "type": "Address" + }, + { + "name": "token", + "type": "EgldOrEsdtTokenIdentifier" + }, + { + "name": "status", + "type": "Status" + }, + { + "name": "note", + "type": "bytes" + } + ] + }, + "LoanInfo": { + "type": "struct", + "fields": [ + { + "name": "id", + "type": "BigUint" + }, + { + "name": "amount", + "type": "BigUint" + }, + { + "name": "loaner_address", + "type": "Address" + }, + { + "name": "token", + "type": "EgldOrEsdtTokenIdentifier" + }, + { + "name": "timestamp", + "type": "u64" + }, + { + "name": "status", + "type": "Status" + }, + { + "name": "note", + "type": "bytes" + } + ] + }, + "LoanShortInfo": { + "type": "struct", + "fields": [ + { + "name": "id", + "type": "BigUint" + }, + { + "name": "amount", + "type": "BigUint" + }, + { + "name": "loaner_address", + "type": "Address" + }, + { + "name": "token", + "type": "EgldOrEsdtTokenIdentifier" + } + ] + }, + "Status": { + "type": "enum", + "variants": [ + { + "name": "Redeemed", + "discriminant": 0 + }, + { + "name": "NotRedeemed", + "discriminant": 1 + } + ] + } + } +} diff --git a/src/testdata/custom-types-out-of-order-D.abi.json b/src/testdata/custom-types-out-of-order-D.abi.json new file mode 100644 index 00000000..b93add57 --- /dev/null +++ b/src/testdata/custom-types-out-of-order-D.abi.json @@ -0,0 +1,923 @@ +{ + "name": "Issue_237_Part_2", + "constructor": { + "inputs": [], + "outputs": [] + }, + "endpoints": [ + { + "name": "donate_permanent", + "mutability": "mutable", + "payableInTokens": [ + "EGLD" + ], + "inputs": [], + "outputs": [] + }, + { + "name": "donate_temporary", + "mutability": "mutable", + "payableInTokens": [ + "EGLD" + ], + "inputs": [], + "outputs": [] + }, + { + "name": "get_user_overview", + "mutability": "readonly", + "inputs": [ + { + "name": "wallet_address", + "type": "Address" + } + ], + "outputs": [ + { + "type": "UserOverview" + } + ] + }, + { + "name": "get_donations_overview", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "DonationsOverview" + } + ] + }, + { + "name": "set_staking_provider", + "onlyOwner": true, + "mutability": "mutable", + "inputs": [ + { + "name": "staking_provider", + "type": "Address" + }, + { + "name": "unbonding_duration", + "type": "u8" + } + ], + "outputs": [] + }, + { + "name": "unbond", + "mutability": "mutable", + "inputs": [ + { + "name": "amount_to_unbond", + "type": "BigUint" + } + ], + "outputs": [] + }, + { + "name": "initiate_withdraw", + "mutability": "mutable", + "inputs": [], + "outputs": [] + }, + { + "name": "finalize_withdrawal", + "mutability": "mutable", + "inputs": [], + "outputs": [] + }, + { + "name": "claimRewards", + "onlyOwner": true, + "mutability": "mutable", + "inputs": [], + "outputs": [] + }, + { + "name": "reinvestRewards", + "onlyOwner": true, + "mutability": "mutable", + "inputs": [], + "outputs": [] + }, + { + "name": "unbondRewards", + "onlyOwner": true, + "mutability": "mutable", + "inputs": [], + "outputs": [] + }, + { + "name": "moveUnbondedRewardsToSc", + "onlyOwner": true, + "mutability": "mutable", + "inputs": [], + "outputs": [] + }, + { + "name": "moveUnbondedRewardsBackToRewardsPile", + "onlyOwner": true, + "mutability": "mutable", + "inputs": [], + "outputs": [] + }, + { + "name": "withdrawRewards", + "onlyOwner": true, + "mutability": "mutable", + "inputs": [], + "outputs": [] + }, + { + "name": "get_staking_provider_address", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "Address" + } + ] + }, + { + "name": "get_staking_unbonding_duration", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "u8" + } + ] + }, + { + "name": "get_donators", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "variadic
", + "multi_result": true + } + ] + }, + { + "name": "get_total_donated", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "BigUint" + } + ] + }, + { + "name": "get_open_donations", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "BigUint" + } + ] + }, + { + "name": "get_pending_donations", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "BigUint" + } + ] + }, + { + "name": "get_staked_donations", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "BigUint" + } + ] + }, + { + "name": "get_unbonding_donations", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "BigUint" + } + ] + }, + { + "name": "get_permanent_user_donations", + "mutability": "readonly", + "inputs": [ + { + "name": "wallet_address", + "type": "Address" + } + ], + "outputs": [ + { + "type": "BigUint" + } + ] + }, + { + "name": "get_permanent_user_donations_history", + "mutability": "readonly", + "inputs": [ + { + "name": "wallet_address", + "type": "Address" + } + ], + "outputs": [ + { + "type": "variadic", + "multi_result": true + } + ] + }, + { + "name": "get_lended_user_donations", + "mutability": "readonly", + "inputs": [ + { + "name": "wallet_address", + "type": "Address" + } + ], + "outputs": [ + { + "type": "BigUint" + } + ] + }, + { + "name": "get_lended_user_donations_history", + "mutability": "readonly", + "inputs": [ + { + "name": "wallet_address", + "type": "Address" + } + ], + "outputs": [ + { + "type": "variadic", + "multi_result": true + } + ] + }, + { + "name": "get_unbonding_user_donations", + "mutability": "readonly", + "inputs": [ + { + "name": "wallet_address", + "type": "Address" + } + ], + "outputs": [ + { + "type": "variadic", + "multi_result": true + } + ] + }, + { + "name": "get_waiting_unbonding_requesters", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "variadic
", + "multi_result": true + } + ] + }, + { + "name": "get_waiting_unbonding_requests", + "mutability": "readonly", + "inputs": [ + { + "name": "requester", + "type": "Address" + } + ], + "outputs": [ + { + "type": "BigUint" + } + ] + }, + { + "name": "get_initiate_unbonding_pending", + "mutability": "readonly", + "inputs": [ + { + "name": "wallet_address", + "type": "Address" + } + ], + "outputs": [ + { + "type": "BigUint" + } + ] + }, + { + "name": "get_initiate_withdrawal_pending", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "bool" + } + ] + }, + { + "name": "get_epoch_last_withdrawal_initiated", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "u64" + } + ] + }, + { + "name": "get_claimed_rewards_in_sc", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "BigUint" + } + ] + }, + { + "name": "get_claimed_rewards_period", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "BigUint" + } + ] + }, + { + "name": "get_claimed_rewards_alltime", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "BigUint" + } + ] + }, + { + "name": "get_withdrawn_rewards_history", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "variadic", + "multi_result": true + } + ] + }, + { + "name": "get_total_donation_points_for", + "mutability": "readonly", + "inputs": [ + { + "name": "donator", + "type": "Address" + } + ], + "outputs": [ + { + "type": "u64" + } + ] + }, + { + "name": "get_unclaimed_donation_points_for", + "mutability": "readonly", + "inputs": [ + { + "name": "donator", + "type": "Address" + } + ], + "outputs": [ + { + "type": "u64" + } + ] + }, + { + "name": "get_perm_donation_tier_list", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "variadic", + "multi_result": true + } + ] + }, + { + "name": "get_lended_donation_tier_list", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "variadic", + "multi_result": true + } + ] + }, + { + "name": "get_epoch_donation_points_last_updated", + "mutability": "readonly", + "inputs": [ + { + "name": "donator", + "type": "Address" + } + ], + "outputs": [ + { + "type": "u64" + } + ] + }, + { + "name": "get_donation_points", + "mutability": "readonly", + "inputs": [ + { + "name": "donator", + "type": "Address" + } + ], + "outputs": [ + { + "type": "u64" + } + ] + }, + { + "name": "createAuction", + "mutability": "mutable", + "payableInTokens": [ + "*" + ], + "inputs": [ + { + "name": "auction_start", + "type": "u64" + }, + { + "name": "auction_end", + "type": "u64" + }, + { + "name": "minimum_bid", + "type": "BigUint" + } + ], + "outputs": [] + }, + { + "name": "cancelAuction", + "mutability": "mutable", + "inputs": [ + { + "name": "auction_id", + "type": "u64" + } + ], + "outputs": [] + }, + { + "name": "auctionBid", + "mutability": "mutable", + "payableInTokens": [ + "EGLD" + ], + "inputs": [ + { + "name": "auction_id", + "type": "u64" + } + ], + "outputs": [] + }, + { + "name": "auctionClaim", + "mutability": "mutable", + "inputs": [ + { + "name": "auction_id", + "type": "u64" + } + ], + "outputs": [] + }, + { + "name": "get_all_active_auctions", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "List" + } + ] + }, + { + "name": "get_highest_auction_id", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "u64" + } + ] + }, + { + "name": "get_auctions", + "mutability": "readonly", + "inputs": [ + { + "name": "auction_id", + "type": "u64" + } + ], + "outputs": [ + { + "type": "Auction" + } + ] + }, + { + "name": "get_all_auctions", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "variadic", + "multi_result": true + } + ] + }, + { + "name": "get_active_auctions", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "variadic", + "multi_result": true + } + ] + }, + { + "name": "get_my_active_auctions", + "mutability": "readonly", + "inputs": [ + { + "name": "creator", + "type": "Address" + } + ], + "outputs": [ + { + "type": "variadic", + "multi_result": true + } + ] + }, + { + "name": "get_highest_bid_auction", + "mutability": "readonly", + "inputs": [ + { + "name": "auction_id", + "type": "u64" + } + ], + "outputs": [ + { + "type": "BigUint" + } + ] + }, + { + "name": "get_highest_bidder_auction", + "mutability": "readonly", + "inputs": [ + { + "name": "auction_id", + "type": "u64" + } + ], + "outputs": [ + { + "type": "Address" + } + ] + }, + { + "name": "get_fee_per_thousand_auction", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "u32" + } + ] + } + ], + "events": [ + { + "identifier": "skipped_because_already_withdrawn", + "inputs": [ + { + "name": "from", + "type": "Address", + "indexed": true + }, + { + "name": "epoch", + "type": "u64", + "indexed": true + } + ] + } + ], + "hasCallback": true, + "types": { + "Auction": { + "type": "struct", + "fields": [ + { + "name": "auction_id", + "type": "u64" + }, + { + "name": "creator", + "type": "Address" + }, + { + "name": "token_identifier", + "type": "EgldOrEsdtTokenIdentifier" + }, + { + "name": "nonce", + "type": "u64" + }, + { + "name": "auction_start", + "type": "u64" + }, + { + "name": "auction_end", + "type": "u64" + }, + { + "name": "minimum_bid", + "type": "BigUint" + } + ] + }, + "AuctionItem": { + "type": "struct", + "fields": [ + { + "name": "auction_id", + "type": "u64" + }, + { + "name": "creator", + "type": "Address" + }, + { + "name": "auction_start", + "type": "u64" + }, + { + "name": "auction_end", + "type": "u64" + }, + { + "name": "minimum_bid", + "type": "BigUint" + }, + { + "name": "highest_bid", + "type": "BigUint" + }, + { + "name": "highest_bidder", + "type": "Option
" + }, + { + "name": "nft_data", + "type": "NftData" + } + ] + }, + "DonationData": { + "type": "struct", + "fields": [ + { + "name": "donation", + "type": "BigUint" + }, + { + "name": "timestamp", + "type": "u64" + } + ] + }, + "DonationHistory": { + "type": "struct", + "fields": [ + { + "name": "donation_data", + "type": "DonationData" + }, + { + "name": "donation_type", + "type": "DonationType" + } + ] + }, + "DonationTier": { + "type": "struct", + "fields": [ + { + "name": "minimum_required", + "type": "BigUint" + }, + { + "name": "donatiom_point_multiplier", + "type": "u8" + } + ] + }, + "DonationType": { + "type": "enum", + "variants": [ + { + "name": "Deposit", + "discriminant": 0 + }, + { + "name": "Withdraw", + "discriminant": 1 + }, + { + "name": "ReinvestRewards", + "discriminant": 2 + }, + { + "name": "WithdrawRewards", + "discriminant": 3 + }, + { + "name": "AuctionFee", + "discriminant": 4 + } + ] + }, + "DonationsOverview": { + "type": "struct", + "fields": [ + { + "name": "donators", + "type": "u32" + }, + { + "name": "total_donated", + "type": "BigUint" + }, + { + "name": "open_donations", + "type": "BigUint" + }, + { + "name": "staked_donations", + "type": "BigUint" + }, + { + "name": "rewards_alltime", + "type": "BigUint" + }, + { + "name": "rewards_period", + "type": "BigUint" + }, + { + "name": "rewards_pending", + "type": "BigUint" + }, + { + "name": "rewards_withdraw_history", + "type": "List" + } + ] + }, + "NftData": { + "type": "struct", + "fields": [ + { + "name": "name", + "type": "bytes" + }, + { + "name": "collection", + "type": "TokenIdentifier" + }, + { + "name": "nonce", + "type": "u64" + }, + { + "name": "royalties", + "type": "BigUint" + }, + { + "name": "uris", + "type": "List" + } + ] + }, + "RewardsHistory": { + "type": "struct", + "fields": [ + { + "name": "amount", + "type": "BigUint" + }, + { + "name": "withdrawn_timestamp", + "type": "u64" + } + ] + }, + "UnbondingData": { + "type": "struct", + "fields": [ + { + "name": "amount", + "type": "BigUint" + }, + { + "name": "epoch", + "type": "u64" + } + ] + }, + "UserDonation": { + "type": "struct", + "fields": [ + { + "name": "donator", + "type": "Address" + }, + { + "name": "donations_permanent", + "type": "BigUint" + }, + { + "name": "donations_lended", + "type": "BigUint" + }, + { + "name": "withdrawal_requests", + "type": "List" + } + ] + }, + "UserOverview": { + "type": "struct", + "fields": [ + { + "name": "my_donations", + "type": "UserDonation" + }, + { + "name": "my_donation_points", + "type": "u64" + } + ] + } + } +} From 9c573c332017828fc0e1cf1d1b4e6338a3b1f840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Sun, 12 Feb 2023 23:39:19 +0200 Subject: [PATCH 02/11] Fix type mapping. Remove sorting logic, use a depth-first approach for custom types. --- src/smartcontracts/typesystem/abiRegistry.ts | 48 ++++++++++---------- src/smartcontracts/typesystem/typeMapper.ts | 14 ++++-- 2 files changed, 36 insertions(+), 26 deletions(-) diff --git a/src/smartcontracts/typesystem/abiRegistry.ts b/src/smartcontracts/typesystem/abiRegistry.ts index 9a964dc7..d5fe56ec 100644 --- a/src/smartcontracts/typesystem/abiRegistry.ts +++ b/src/smartcontracts/typesystem/abiRegistry.ts @@ -1,11 +1,11 @@ import * as errors from "../../errors"; import { guardValueIsSetWithMessage } from "../../utils"; -import { StructType } from "./struct"; import { ContractInterface } from "./contractInterface"; -import { CustomType } from "./types"; +import { EndpointDefinition, EndpointParameterDefinition } from "./endpoint"; import { EnumType } from "./enum"; +import { StructType } from "./struct"; import { TypeMapper } from "./typeMapper"; -import { EndpointDefinition, EndpointParameterDefinition } from "./endpoint"; +import { CustomType } from "./types"; export class AbiRegistry { readonly interfaces: ContractInterface[] = []; @@ -33,8 +33,6 @@ export class AbiRegistry { this.customTypes.push(customType); } - this.sortCustomTypesByDependencies(); - return this; } @@ -48,20 +46,6 @@ export class AbiRegistry { throw new errors.ErrTypingSystem(`Unknown type discriminant: ${typeDiscriminant}`); } - private sortCustomTypesByDependencies() { - // TODO: Improve consistency of the sorting function (and make sure the sorting is stable): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort - this.customTypes.sort((a: CustomType, b: CustomType) => { - const bDependsOnA = b.getNamesOfDependencies().indexOf(a.getName()) > -1; - if (bDependsOnA) { - // Sort "a" before "b". - return -1; - } - - // Sort "b" before "a". - return 1; - }); - } - getInterface(name: string): ContractInterface { let result = this.interfaces.find((e) => e.name == name); guardValueIsSetWithMessage(`interface [${name}] not found`, result); @@ -109,13 +93,11 @@ export class AbiRegistry { // First, remap custom types (actually, under the hood, this will remap types of struct fields) for (const type of this.customTypes) { - const mappedTyped = mapper.mapType(type); - newCustomTypes.push(mappedTyped); + this.mapCustomTypeDepthFirst(type, this.customTypes, mapper, newCustomTypes); } // Then, remap types of all endpoint parameters. - // But we'll use an enhanced mapper, that takes into account the results from the previous step. - mapper = new TypeMapper(newCustomTypes); + // The mapper learned all necessary types in the previous step. for (const iface of this.interfaces) { let newEndpoints: EndpointDefinition[] = []; for (const endpoint of iface.endpoints) { @@ -132,6 +114,26 @@ export class AbiRegistry { return newRegistry; } + + private mapCustomTypeDepthFirst(typeToMap: CustomType, allTypesToMap: CustomType[], mapper: TypeMapper, mappedTypes: CustomType[]) { + const hasBeenMapped = mappedTypes.findIndex(type => type.getName() == typeToMap.getName()) >= 0; + if (hasBeenMapped) { + return; + } + + for (const typeName of typeToMap.getNamesOfDependencies()) { + const dependencyType = allTypesToMap.find(type => type.getName() == typeName); + if (!dependencyType) { + // It's a type that we don't have to map (e.g. could be a primitive type). + continue; + } + + this.mapCustomTypeDepthFirst(dependencyType, allTypesToMap, mapper, mappedTypes) + } + + const mappedType = mapper.mapType(typeToMap); + mappedTypes.push(mappedType); + } } function mapEndpoint(endpoint: EndpointDefinition, mapper: TypeMapper): EndpointDefinition { diff --git a/src/smartcontracts/typesystem/typeMapper.ts b/src/smartcontracts/typesystem/typeMapper.ts index 5962536c..2f30eafe 100644 --- a/src/smartcontracts/typesystem/typeMapper.ts +++ b/src/smartcontracts/typesystem/typeMapper.ts @@ -103,10 +103,18 @@ export class TypeMapper { } } + /** + * Maps a "raw type" object to a "known (specific) type" object. + * In the process, it also learns the new type. + * Can only map types if their dependencies were previously learned (through mapping). + */ mapType(type: Type): Type { - let mappedType = this.mapRecursiveType(type); + let mappedType = this.mapTypeRecursively(type); if (mappedType) { - // We do not learn generic types (that also have type parameters) + // We do not learn generic types (that also have type parameters), + // we only learn closed, non-generic types. + // Reason: in the ABI, generic types are unnamed. + // E.g.: two occurrences of List aren't recognized as a single type (simplification). if (!mappedType.isGenericType()) { this.learnType(mappedType); } @@ -117,7 +125,7 @@ export class TypeMapper { throw new errors.ErrTypingSystem(`Cannot map the type "${type.getName()}" to a known type`); } - mapRecursiveType(type: Type): Type | null { + private mapTypeRecursively(type: Type): Type | null { let isGeneric = type.isGenericType(); let previouslyLearnedType = this.learnedTypesMap.get(type.getName()); From 85a80b487921bcf60b806649e44909aa86942dad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Sun, 12 Feb 2023 23:40:39 +0200 Subject: [PATCH 03/11] Bump version. --- .github/workflows/package-lock-checks.yml | 2 -- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/package-lock-checks.yml b/.github/workflows/package-lock-checks.yml index a04246a0..7490c457 100644 --- a/.github/workflows/package-lock-checks.yml +++ b/.github/workflows/package-lock-checks.yml @@ -1,8 +1,6 @@ name: Check package-lock.json on: - pull_request: - branches: [ main ] workflow_dispatch: jobs: diff --git a/package-lock.json b/package-lock.json index 627a369f..5444c121 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@multiversx/sdk-core", - "version": "11.2.0", + "version": "11.2.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@multiversx/sdk-core", - "version": "11.2.0", + "version": "11.2.1", "license": "MIT", "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", diff --git a/package.json b/package.json index e61eed1e..89bbd6e5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@multiversx/sdk-core", - "version": "11.2.0", + "version": "11.2.1", "description": "MultiversX SDK for JavaScript and TypeScript", "main": "out/index.js", "types": "out/index.d.js", From ad075661ac867d97c27ba64fb98151251e5b346f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Mon, 13 Feb 2023 09:08:21 +0200 Subject: [PATCH 04/11] Improve tests. --- .../typesystem/abiRegistry.spec.ts | 25 ++++++++++++------- src/smartcontracts/typesystem/abiRegistry.ts | 4 +++ ...n => custom-types-out-of-order-a.abi.json} | 0 ...n => custom-types-out-of-order-b.abi.json} | 0 ...n => custom-types-out-of-order-c.abi.json} | 0 ...n => custom-types-out-of-order-d.abi.json} | 0 6 files changed, 20 insertions(+), 9 deletions(-) rename src/testdata/{custom-types-out-of-order-A.abi.json => custom-types-out-of-order-a.abi.json} (100%) rename src/testdata/{custom-types-out-of-order-B.abi.json => custom-types-out-of-order-b.abi.json} (100%) rename src/testdata/{custom-types-out-of-order-C.abi.json => custom-types-out-of-order-c.abi.json} (100%) rename src/testdata/{custom-types-out-of-order-D.abi.json => custom-types-out-of-order-d.abi.json} (100%) diff --git a/src/smartcontracts/typesystem/abiRegistry.spec.ts b/src/smartcontracts/typesystem/abiRegistry.spec.ts index 47cc4fdf..1754ecfc 100644 --- a/src/smartcontracts/typesystem/abiRegistry.spec.ts +++ b/src/smartcontracts/typesystem/abiRegistry.spec.ts @@ -98,8 +98,8 @@ describe("test abi registry", () => { assert.equal(dummyType.getFieldDefinition("raw")!.type.getClassName(), ArrayVecType.ClassName); }); - it("should load ABI when custom types are out of order (A)", async () => { - const registry = await loadAbiRegistry("src/testdata/custom-types-out-of-order-A.abi.json"); + it("should load ABI when custom types are out of order (a)", async () => { + const registry = await loadAbiRegistry("src/testdata/custom-types-out-of-order-a.abi.json"); assert.deepEqual(registry.getStruct("EsdtTokenPayment").getNamesOfDependencies(), ["EsdtTokenType", "TokenIdentifier", "u64", "BigUint"]); assert.deepEqual(registry.getEnum("EsdtTokenType").getNamesOfDependencies(), []); @@ -108,8 +108,8 @@ describe("test abi registry", () => { assert.deepEqual(registry.getStruct("TypeC").getNamesOfDependencies(), ["u64"]); }); - it("should load ABI when custom types are out of order (B)", async () => { - const registry = await loadAbiRegistry("src/testdata/custom-types-out-of-order-B.abi.json"); + it("should load ABI when custom types are out of order (b)", async () => { + const registry = await loadAbiRegistry("src/testdata/custom-types-out-of-order-b.abi.json"); assert.deepEqual(registry.getStruct("EsdtTokenPayment").getNamesOfDependencies(), ["EsdtTokenType", "TokenIdentifier", "u64", "BigUint"]); assert.deepEqual(registry.getEnum("EsdtTokenType").getNamesOfDependencies(), []); @@ -118,11 +118,18 @@ describe("test abi registry", () => { assert.deepEqual(registry.getStruct("TypeC").getNamesOfDependencies(), ["u64"]); }); - it("should load ABI when custom types are out of order (C, D)", async () => { - const c = await loadAbiRegistry("src/testdata/custom-types-out-of-order-C.abi.json"); - assert.lengthOf(c.customTypes, 5); + it("should load ABI when custom types are out of order (community example: c)", async () => { + const registry = await loadAbiRegistry("src/testdata/custom-types-out-of-order-c.abi.json"); - const d = await loadAbiRegistry("src/testdata/custom-types-out-of-order-D.abi.json"); - assert.lengthOf(d.customTypes, 12); + assert.lengthOf(registry.customTypes, 5); + assert.deepEqual(registry.getStruct("LoanCreateOptions").getNamesOfDependencies(), ["BigUint", "Address", "TokenIdentifier", "Status", "bytes"]); + + }); + + it("should load ABI when custom types are out of order (community example: d)", async () => { + const registry = await loadAbiRegistry("src/testdata/custom-types-out-of-order-d.abi.json"); + + assert.lengthOf(registry.customTypes, 12); + assert.deepEqual(registry.getStruct("AuctionItem").getNamesOfDependencies(), ["u64", "Address", "BigUint", "Option", "NftData", "bytes", "TokenIdentifier", "List"]); }); }); diff --git a/src/smartcontracts/typesystem/abiRegistry.ts b/src/smartcontracts/typesystem/abiRegistry.ts index d5fe56ec..2b21afad 100644 --- a/src/smartcontracts/typesystem/abiRegistry.ts +++ b/src/smartcontracts/typesystem/abiRegistry.ts @@ -96,6 +96,10 @@ export class AbiRegistry { this.mapCustomTypeDepthFirst(type, this.customTypes, mapper, newCustomTypes); } + if (this.customTypes.length != newCustomTypes.length) { + throw new errors.ErrTypingSystem("Did not re-map all custom types"); + } + // Then, remap types of all endpoint parameters. // The mapper learned all necessary types in the previous step. for (const iface of this.interfaces) { diff --git a/src/testdata/custom-types-out-of-order-A.abi.json b/src/testdata/custom-types-out-of-order-a.abi.json similarity index 100% rename from src/testdata/custom-types-out-of-order-A.abi.json rename to src/testdata/custom-types-out-of-order-a.abi.json diff --git a/src/testdata/custom-types-out-of-order-B.abi.json b/src/testdata/custom-types-out-of-order-b.abi.json similarity index 100% rename from src/testdata/custom-types-out-of-order-B.abi.json rename to src/testdata/custom-types-out-of-order-b.abi.json diff --git a/src/testdata/custom-types-out-of-order-C.abi.json b/src/testdata/custom-types-out-of-order-c.abi.json similarity index 100% rename from src/testdata/custom-types-out-of-order-C.abi.json rename to src/testdata/custom-types-out-of-order-c.abi.json diff --git a/src/testdata/custom-types-out-of-order-D.abi.json b/src/testdata/custom-types-out-of-order-d.abi.json similarity index 100% rename from src/testdata/custom-types-out-of-order-D.abi.json rename to src/testdata/custom-types-out-of-order-d.abi.json From 6bb3ca4a0da2c60116a07fe26494b76750e6ef01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Mon, 13 Feb 2023 09:22:25 +0200 Subject: [PATCH 05/11] Configure BigNumber's EXPONENTIAL_AT. --- src/tokenPayment.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tokenPayment.ts b/src/tokenPayment.ts index 5552dbd2..cb2c8bf3 100644 --- a/src/tokenPayment.ts +++ b/src/tokenPayment.ts @@ -5,7 +5,7 @@ const EGLDTokenIdentifier = "EGLD"; const EGLDNumDecimals = 18; // Note: this will actually set the default rounding mode for all BigNumber objects in the environment (in the application / dApp). -BigNumber.set({ ROUNDING_MODE: 1 }); +BigNumber.set({ ROUNDING_MODE: 1, EXPONENTIAL_AT: 128 }); export class TokenPayment { readonly tokenIdentifier: string; From 3cfbdf5f75360f47bf3fc133ae6736a17111220c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Mon, 13 Feb 2023 09:23:24 +0200 Subject: [PATCH 06/11] Adjust unit tests, so that BigNumber's aren't passed as value anymore - they were serving as bad examples. --- src/proto/serializer.spec.ts | 15 +++++++-------- src/transaction.spec.ts | 7 +++---- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/proto/serializer.spec.ts b/src/proto/serializer.spec.ts index 3ba47bbc..6d2a513b 100644 --- a/src/proto/serializer.spec.ts +++ b/src/proto/serializer.spec.ts @@ -1,12 +1,11 @@ import { assert } from "chai"; -import { ProtoSerializer } from "./serializer"; -import { Transaction } from "../transaction"; -import { loadTestWallets, TestWallet } from "../testutils"; -import { Signature } from "../signature"; import { TransactionVersion } from "../networkParams"; -import { TransactionPayload } from "../transactionPayload"; -import BigNumber from "bignumber.js"; +import { Signature } from "../signature"; +import { loadTestWallets, TestWallet } from "../testutils"; import { TokenPayment } from "../tokenPayment"; +import { Transaction } from "../transaction"; +import { TransactionPayload } from "../transactionPayload"; +import { ProtoSerializer } from "./serializer"; describe("serialize transactions", () => { let wallets: Record; @@ -69,7 +68,7 @@ describe("serialize transactions", () => { it("with data, with large value", async () => { let transaction = new Transaction({ nonce: 92, - value: new BigNumber("123456789000000000000000000000"), + value: "123456789000000000000000000000", sender: wallets.alice.address, receiver: wallets.bob.address, gasLimit: 100000, @@ -86,7 +85,7 @@ describe("serialize transactions", () => { it("with nonce = 0", async () => { let transaction = new Transaction({ nonce: 0, - value: new BigNumber("0"), + value: "0", sender: wallets.alice.address, receiver: wallets.bob.address, gasLimit: 80000, diff --git a/src/transaction.spec.ts b/src/transaction.spec.ts index 4b1fad9b..fb68e5ce 100644 --- a/src/transaction.spec.ts +++ b/src/transaction.spec.ts @@ -1,10 +1,9 @@ import { assert } from "chai"; -import BigNumber from "bignumber.js"; -import { Transaction } from "./transaction"; import { TransactionOptions, TransactionVersion } from "./networkParams"; -import { TransactionPayload } from "./transactionPayload"; import { loadTestWallets, TestWallet } from "./testutils"; import { TokenPayment } from "./tokenPayment"; +import { Transaction } from "./transaction"; +import { TransactionPayload } from "./transactionPayload"; describe("test transaction construction", async () => { @@ -187,7 +186,7 @@ describe("test transaction construction", async () => { const sender = wallets.alice.address; const transaction = new Transaction({ nonce: 90, - value: new BigNumber("1000000000000000000"), + value: "123456789000000000000000000000", sender: wallets.alice.address, receiver: wallets.bob.address, gasPrice: minGasPrice, From adeb672eb39f946115d8e29d5e18c8e4bf544ef8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Mon, 13 Feb 2023 09:24:23 +0200 Subject: [PATCH 07/11] Handle cases when developers still pass BigNumber objects as values (not recommended). --- src/transaction.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/transaction.ts b/src/transaction.ts index 9aded117..3517c321 100644 --- a/src/transaction.ts +++ b/src/transaction.ts @@ -242,7 +242,7 @@ export class Transaction { static fromPlainObject(plainObjectTransaction: IPlainTransactionObject): Transaction { const tx = new Transaction({ nonce: Number(plainObjectTransaction.nonce), - value: new BigNumber(plainObjectTransaction.value), + value: new BigNumber(plainObjectTransaction.value).toFixed(0), receiver: Address.fromString(plainObjectTransaction.receiver), sender: Address.fromString(plainObjectTransaction.sender), gasPrice: Number(plainObjectTransaction.gasPrice), From d4a1c7b6a9f89098dce64cfbbd6ae230d0ced9a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Mon, 13 Feb 2023 09:31:56 +0200 Subject: [PATCH 08/11] Handle cases where users pass BigNumber as tx.value. --- src/transaction.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/transaction.ts b/src/transaction.ts index 3517c321..af639a1e 100644 --- a/src/transaction.ts +++ b/src/transaction.ts @@ -108,7 +108,7 @@ export class Transaction { options?: TransactionOptions; }) { this.nonce = nonce || 0; - this.value = value || 0; + this.value = value ? new BigNumber(value.toString()).toFixed(0) : 0; this.sender = sender; this.receiver = receiver; this.gasPrice = gasPrice || TRANSACTION_MIN_GAS_PRICE; From 1e97054da842fa1a068fc6e2fe664a57ec67a6e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Mon, 13 Feb 2023 09:56:57 +0200 Subject: [PATCH 09/11] Add more tests. Undo configuring the "EXPONENTIAL_AT". --- src/tokenPayment.ts | 2 +- src/transaction.spec.ts | 36 +++++++++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/tokenPayment.ts b/src/tokenPayment.ts index cb2c8bf3..5552dbd2 100644 --- a/src/tokenPayment.ts +++ b/src/tokenPayment.ts @@ -5,7 +5,7 @@ const EGLDTokenIdentifier = "EGLD"; const EGLDNumDecimals = 18; // Note: this will actually set the default rounding mode for all BigNumber objects in the environment (in the application / dApp). -BigNumber.set({ ROUNDING_MODE: 1, EXPONENTIAL_AT: 128 }); +BigNumber.set({ ROUNDING_MODE: 1 }); export class TokenPayment { readonly tokenIdentifier: string; diff --git a/src/transaction.spec.ts b/src/transaction.spec.ts index fb68e5ce..2a170bcd 100644 --- a/src/transaction.spec.ts +++ b/src/transaction.spec.ts @@ -1,3 +1,4 @@ +import BigNumber from "bignumber.js"; import { assert } from "chai"; import { TransactionOptions, TransactionVersion } from "./networkParams"; import { loadTestWallets, TestWallet } from "./testutils"; @@ -187,7 +188,7 @@ describe("test transaction construction", async () => { const transaction = new Transaction({ nonce: 90, value: "123456789000000000000000000000", - sender: wallets.alice.address, + sender: sender, receiver: wallets.bob.address, gasPrice: minGasPrice, gasLimit: 80000, @@ -199,4 +200,37 @@ describe("test transaction construction", async () => { const restoredTransaction = Transaction.fromPlainObject(plainObject); assert.deepEqual(restoredTransaction, transaction); }); + + it("should handle large values", () => { + const tx1 = new Transaction({ + value: "123456789000000000000000000000", + sender: wallets.alice.address, + receiver: wallets.bob.address, + gasLimit: 50000, + chainID: "local-testnet" + }); + assert.equal(tx1.getValue().toString(), "123456789000000000000000000000"); + + const tx2 = new Transaction({ + value: TokenPayment.egldFromBigInteger("123456789000000000000000000000"), + sender: wallets.alice.address, + receiver: wallets.bob.address, + gasLimit: 50000, + chainID: "local-testnet" + }); + assert.equal(tx2.getValue().toString(), "123456789000000000000000000000"); + + const tx3 = new Transaction({ + // Passing a BigNumber is not recommended. + // However, ITransactionValue interface is permissive, and developers may mistakenly pass such objects as values. + // TokenPayment objects or simple strings (see above) are preferred, instead. + value: new BigNumber("123456789000000000000000000000"), + sender: wallets.alice.address, + receiver: wallets.bob.address, + gasLimit: 50000, + chainID: "local-testnet" + }); + assert.equal(tx3.getValue().toString(), "123456789000000000000000000000"); + }); + }); From 29db4a387752c37449a814f99351e81f6923b58c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Mon, 13 Feb 2023 10:14:02 +0200 Subject: [PATCH 10/11] Formatting. --- src/transaction.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/transaction.spec.ts b/src/transaction.spec.ts index 2a170bcd..f6f2f9be 100644 --- a/src/transaction.spec.ts +++ b/src/transaction.spec.ts @@ -232,5 +232,4 @@ describe("test transaction construction", async () => { }); assert.equal(tx3.getValue().toString(), "123456789000000000000000000000"); }); - }); From 859f5a26140fbd305c3a5c91abec4c932ce863f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Mon, 13 Feb 2023 10:27:41 +0200 Subject: [PATCH 11/11] Bump version. --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5444c121..fbb2794a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@multiversx/sdk-core", - "version": "11.2.1", + "version": "11.3.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@multiversx/sdk-core", - "version": "11.2.1", + "version": "11.3.0", "license": "MIT", "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", diff --git a/package.json b/package.json index 89bbd6e5..24d7bfc2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@multiversx/sdk-core", - "version": "11.2.1", + "version": "11.3.0", "description": "MultiversX SDK for JavaScript and TypeScript", "main": "out/index.js", "types": "out/index.d.js",