diff --git a/packages/chainutil/runvm.go b/packages/chainutil/runvm.go index 0625f46675..2abfda1888 100644 --- a/packages/chainutil/runvm.go +++ b/packages/chainutil/runvm.go @@ -31,7 +31,7 @@ func runISCTask( TimeAssumption: blockTime, Entropy: hashing.PseudoRandomHash(nil), ValidatorFeeTarget: accounts.CommonAccount(), - EnableGasBurnLogging: false, + EnableGasBurnLogging: estimateGasMode, EstimateGasMode: estimateGasMode, EVMTracer: evmTracer, Log: ch.Log().Desugar().WithOptions(zap.AddCallerSkip(1)).Sugar(), diff --git a/tools/cluster/cluster.go b/tools/cluster/cluster.go index 8fe65fd552..f08597b07b 100644 --- a/tools/cluster/cluster.go +++ b/tools/cluster/cluster.go @@ -872,3 +872,39 @@ func (clu *Cluster) AssertAddressBalances(addr iotago.Address, expected *isc.Ass func (clu *Cluster) GetOutputs(addr iotago.Address) (map[iotago.OutputID]iotago.Output, error) { return clu.l1.OutputMap(addr) } + +func (clu *Cluster) MintL1NFT(immutableMetadata []byte, target iotago.Address, issuerKeypair *cryptolib.KeyPair) (iotago.OutputID, *iotago.NFTOutput, error) { + outputsSet, err := clu.l1.OutputMap(issuerKeypair.Address()) + if err != nil { + return iotago.OutputID{}, nil, err + } + tx, err := transaction.NewMintNFTsTransaction(transaction.MintNFTsTransactionParams{ + IssuerKeyPair: issuerKeypair, + CollectionOutputID: nil, + Target: target, + ImmutableMetadata: [][]byte{immutableMetadata}, + UnspentOutputs: outputsSet, + UnspentOutputIDs: isc.OutputSetToOutputIDs(outputsSet), + }) + if err != nil { + return iotago.OutputID{}, nil, err + } + _, err = clu.l1.PostTxAndWaitUntilConfirmation(tx) + if err != nil { + return iotago.OutputID{}, nil, err + } + + // go through the tx and find the newly minted NFT + outputSet, err := tx.OutputsSet() + if err != nil { + return iotago.OutputID{}, nil, err + } + + for oID, o := range outputSet { + if oNFT, ok := o.(*iotago.NFTOutput); ok && oNFT.NFTID.Empty() { + return oID, oNFT, nil + } + } + + return iotago.OutputID{}, nil, fmt.Errorf("inconsistency: couldn't find newly minted NFT in tx") +} diff --git a/tools/cluster/tests/cluster_test.go b/tools/cluster/tests/cluster_test.go index 9e482d8222..719ee01df9 100644 --- a/tools/cluster/tests/cluster_test.go +++ b/tools/cluster/tests/cluster_test.go @@ -68,6 +68,7 @@ func TestClusterMultiNodeCommittee(t *testing.T) { t.Run("inccounter timelock", func(t *testing.T) { run(t, testIncCounterTimelock) }) t.Run("webapi ISC estimategas onledger", func(t *testing.T) { run(t, testEstimateGasOnLedger) }) + t.Run("webapi ISC estimategas onledger NFT", func(t *testing.T) { run(t, testEstimateGasOnLedgerNFT) }) t.Run("webapi ISC estimategas offledger", func(t *testing.T) { run(t, testEstimateGasOffLedger) }) } diff --git a/tools/cluster/tests/estimategas_test.go b/tools/cluster/tests/estimategas_test.go index 8c6b5fdc17..5c96a900ec 100644 --- a/tools/cluster/tests/estimategas_test.go +++ b/tools/cluster/tests/estimategas_test.go @@ -2,9 +2,11 @@ package tests import ( "context" + "strconv" "testing" "time" + "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" "github.com/iotaledger/hive.go/serializer/v2" @@ -53,14 +55,20 @@ func testEstimateGasOnLedger(t *testing.T, env *ChainEnv) { keyPair, _, err := env.Clu.NewKeyPairWithFunds() require.NoError(t, err) + feeCharged, err := strconv.ParseUint(estimatedReceipt.GasFeeCharged, 10, 64) + require.NoError(t, err) + accountsClient := env.Chain.SCClient(accounts.Contract.Hname(), keyPair) par := chainclient.PostRequestParams{ + Transfer: isc.NewAssetsBaseTokens(feeCharged), Args: map[kv.Key][]byte{ accounts.ParamAgentID: isc.NewAgentID(&iotago.Ed25519Address{}).Bytes(), }, Allowance: isc.NewAssetsBaseTokens(5000), } - par.WithGasBudget(1 * isc.Million) + gasBudget, err := strconv.ParseUint(estimatedReceipt.GasBurned, 10, 64) + require.NoError(t, err) + par.WithGasBudget(gasBudget) tx, err := accountsClient.PostRequest(accounts.FuncTransferAllowanceTo.Name, par, @@ -72,6 +80,84 @@ func testEstimateGasOnLedger(t *testing.T, env *ChainEnv) { require.Equal(t, recs[0].GasFeeCharged, estimatedReceipt.GasFeeCharged) } +func testEstimateGasOnLedgerNFT(t *testing.T, env *ChainEnv) { + // estimate on-ledger request, using and NFT with minSD + keyPair, addr, err := env.Clu.NewKeyPairWithFunds() + require.NoError(t, err) + + metadata, err := iotago.DecodeHex("0x7b227374616e64617264223a224952433237222c2276657273696f6e223a2276312e30222c226e616d65223a2254657374416761696e4e667432222c2274797065223a22696d6167652f6a706567222c22757269223a2268747470733a2f2f696d616765732e756e73706c6173682e636f6d2f70686f746f2d313639353539373737383238392d6663316635633731353935383f69786c69623d72622d342e302e3326697869643d4d3377784d6a4133664442384d48787761473930627931775957646c664878386647567566444238664878386641253344253344266175746f3d666f726d6174266669743d63726f7026773d3335343226713d3830227d") + require.NoError(t, err) + + nftID, _, err := env.Clu.MintL1NFT(metadata, addr, keyPair) + require.NoError(t, err) + nft := &isc.NFT{ + ID: iotago.NFTIDFromOutputID(nftID), + Issuer: addr, + Metadata: metadata, + } + + targetAgentID := isc.NewEthereumAddressAgentID(env.Chain.ChainID, common.Address{}) + + output := transaction.NFTOutputFromPostData( + tpkg.RandEd25519Address(), + isc.EmptyContractIdentity(), + isc.RequestParameters{ + Assets: isc.NewEmptyAssets(), + AdjustToMinimumStorageDeposit: true, + TargetAddress: env.Chain.ChainAddress(), + Metadata: &isc.SendMetadata{ + TargetContract: accounts.Contract.Hname(), + EntryPoint: accounts.FuncTransferAllowanceTo.Hname(), + Params: map[kv.Key][]byte{ + accounts.ParamAgentID: targetAgentID.Bytes(), + }, + Allowance: isc.NewEmptyAssets().AddNFTs(nft.ID), + GasBudget: 1 * isc.Million, + }, + Options: isc.SendOptions{ + Expiration: &isc.Expiration{ + Time: time.Now().Add(100 * time.Hour), + ReturnAddress: addr, + }, + }, + }, + nft, + ) + + outputBytes, err := output.Serialize(serializer.DeSeriModePerformLexicalOrdering, nil) + require.NoError(t, err) + + estimatedReceipt, _, err := env.Chain.Cluster.WaspClient(0).ChainsApi.EstimateGasOnledger(context.Background(), + env.Chain.ChainID.String(), + ).Request(apiclient.EstimateGasRequestOnledger{ + OutputBytes: iotago.EncodeHex(outputBytes), + }).Execute() + require.NoError(t, err) + require.Empty(t, estimatedReceipt.ErrorMessage) + + accountsClient := env.Chain.SCClient(accounts.Contract.Hname(), keyPair) + par := chainclient.PostRequestParams{ + Transfer: isc.NewAssetsBaseTokens(output.Deposit()), + Args: map[kv.Key][]byte{ + accounts.ParamAgentID: targetAgentID.Bytes(), + }, + Allowance: isc.NewEmptyAssets().AddNFTs(nft.ID), + NFT: nft, + AutoAdjustStorageDeposit: false, + } + gasBudget, err := strconv.ParseUint(estimatedReceipt.GasBurned, 10, 64) + require.NoError(t, err) + par.WithGasBudget(gasBudget) + + tx, err := accountsClient.PostRequest(accounts.FuncTransferAllowanceTo.Name, par) + require.NoError(t, err) + recs, err := env.Clu.MultiClient().WaitUntilAllRequestsProcessedSuccessfully(env.Chain.ChainID, tx, false, 10*time.Second) + require.NoError(t, err) + require.Equal(t, recs[0].GasBurned, estimatedReceipt.GasBurned) + require.Equal(t, recs[0].GasFeeCharged, estimatedReceipt.GasFeeCharged) + require.Len(t, env.getAccountNFTs(targetAgentID), 1) +} + func testEstimateGasOffLedger(t *testing.T, env *ChainEnv) { // estimate off-ledger request, then send the same request, assert the gas used/fees match keyPair, _, err := env.Clu.NewKeyPairWithFunds() diff --git a/tools/cluster/tests/util.go b/tools/cluster/tests/util.go index c132c03bb5..727612e460 100644 --- a/tools/cluster/tests/util.go +++ b/tools/cluster/tests/util.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" + iotago "github.com/iotaledger/iota.go/v3" "github.com/iotaledger/wasp/clients/apiclient" "github.com/iotaledger/wasp/clients/apiextensions" "github.com/iotaledger/wasp/contracts/native/inccounter" @@ -126,11 +127,28 @@ func (e *ChainEnv) getBalancesOnChain() map[string]*isc.Assets { assets, err := apiextensions.AssetsFromAPIResponse(balance) require.NoError(e.t, err) - ret[string(agentID.Bytes())] = assets + ret[agentID.String()] = assets } return ret } +func (e *ChainEnv) getAccountNFTs(agentID isc.AgentID) []iotago.NFTID { + nftsResp, _, err := e.Chain.Cluster.WaspClient().CorecontractsApi. + AccountsGetAccountNFTIDs(context.Background(), e.Chain.ChainID.String(), agentID.String()). + Execute() + require.NoError(e.t, err) + + ret := make([]iotago.NFTID, len(nftsResp.NftIds)) + for i, nftIDStr := range nftsResp.NftIds { + nftIDBytes, err := iotago.DecodeHex(nftIDStr) + require.NoError(e.t, err) + ret[i] = iotago.NFTID{} + copy(ret[i][:], nftIDBytes) + } + + return ret +} + func (e *ChainEnv) getTotalBalance() *isc.Assets { totalAssets, _, err := e.Chain.Cluster.WaspClient().CorecontractsApi. AccountsGetTotalAssets(context.Background(), e.Chain.ChainID.String()). @@ -147,7 +165,7 @@ func (e *ChainEnv) printAccounts(title string) { allBalances := e.getBalancesOnChain() s := fmt.Sprintf("------------------------------------- %s\n", title) for k, bals := range allBalances { - aid, err := isc.AgentIDFromBytes([]byte(k)) + aid, err := isc.AgentIDFromString(k) require.NoError(e.t, err) s += fmt.Sprintf(" %s\n", aid.String()) s += fmt.Sprintf("%s\n", bals.String())