diff --git a/contracts/remappings.txt b/contracts/remappings.txt index f2384d685..1982d40b1 100644 --- a/contracts/remappings.txt +++ b/contracts/remappings.txt @@ -1,2 +1,3 @@ @subnet-evm-contracts=../subnet-evm/contracts/contracts -@openzeppelin/=lib/openzeppelin-contracts/ \ No newline at end of file +@openzeppelin/=lib/openzeppelin-contracts/ +@ds-test/=lib/forge-std/lib/ds-test/src/ \ No newline at end of file diff --git a/contracts/src/CrossChainApplications/NativeTokenBridge/NativeTokenMinter.sol b/contracts/src/CrossChainApplications/NativeTokenBridge/NativeTokenMinter.sol index 8dd49eca9..085a0aeec 100644 --- a/contracts/src/CrossChainApplications/NativeTokenBridge/NativeTokenMinter.sol +++ b/contracts/src/CrossChainApplications/NativeTokenBridge/NativeTokenMinter.sol @@ -103,14 +103,16 @@ contract NativeTokenMinter is ITeleporterReceiver, INativeTokenMinter, Reentranc // Lock tokens in this bridge instance. Supports "fee/burn on transfer" ERC20 token // implementations by only bridging the actual balance increase reflected by the call // to transferFrom. - uint256 adjustedAmount = SafeERC20TransferFrom.safeTransferFrom( - IERC20(feeTokenContractAddress), - feeAmount - ); - - // Ensure that the adjusted amount is greater than the fee to be paid. - if (adjustedAmount <= feeAmount) { - revert InsufficientAdjustedAmount(adjustedAmount, feeAmount); + if (feeAmount > 0) { + uint256 adjustedAmount = SafeERC20TransferFrom.safeTransferFrom( + IERC20(feeTokenContractAddress), + feeAmount + ); + + // Ensure that the adjusted amount is greater than the fee to be paid. + if (adjustedAmount <= feeAmount) { + revert InsufficientAdjustedAmount(adjustedAmount, feeAmount); + } } // Burn native token by sending to BLACKHOLE_ADDRESS diff --git a/tests/e2e_test.go b/tests/e2e_test.go index 2c039dd02..b59c6edbd 100644 --- a/tests/e2e_test.go +++ b/tests/e2e_test.go @@ -314,10 +314,7 @@ var _ = ginkgo.BeforeSuite(func() { "--constructor-args", teleporterContractAddress.Hex(), hexutil.Encode(chainBIDInt.Bytes())) cmd.Dir = "./contracts" - fmt.Println(cmd.Args) - // time.Sleep(10000 * time.Second) - output, err := cmd.Output() - fmt.Println(output) + err := cmd.Run() Expect(err).Should(BeNil()) } { @@ -329,10 +326,9 @@ var _ = ginkgo.BeforeSuite(func() { "--private-key", hexutil.Encode(nativeTokenBridgeDeployerPK.D.Bytes()), "--constructor-args", teleporterContractAddress.Hex(), hexutil.Encode(chainAIDInt.Bytes())) - + cmd.Dir = "./contracts" - output, err := cmd.Output() - fmt.Println(output) + err := cmd.Run() Expect(err).Should(BeNil()) } log.Info("Finished deploying Bridge contracts") @@ -605,3 +601,226 @@ var _ = ginkgo.Describe("[Teleporter one way send]", ginkgo.Ordered, func() { }) }) + +// Ginkgo describe node that acts as a container for the teleporter e2e tests. This test suite +// will run through the following steps in order: +// 1. +var _ = ginkgo.Describe("[Native Token Bridge one way send]", ginkgo.Ordered, func() { + // Send a transaction to Subnet A to issue a Warp Message from the Teleporter contract to Subnet B + ginkgo.It("Send Message from A to B", ginkgo.Label("NativeTokenBridge", "SendNativeTokenBridge"), func() { + ctx := context.Background() + + cmd := exec.CommandContext( + ctx, + "cast", + "send", + nativeTokenBridgeContractAddress.Hex(), + "bridgeTokens(address,address,uint256)", + fundedAddress.Hex(), + fundedAddress.Hex(), // TODO use different address + "0x00", + "--rpc-url", chainARPCURI, + "--private-key", hexutil.Encode(nativeTokenBridgeDeployerPK.D.Bytes())) + + err := cmd.Run() + Expect(err).Should(BeNil()) + + // Sleep to ensure the new block is published to the subscriber + time.Sleep(5 * time.Second) + + }) + + // ginkgo.It("Relay message to destination", ginkgo.Label("Teleporter", "RelayMessage"), func() { + // ctx := context.Background() + + // // Get the latest block from Subnet A, and retrieve the warp message from the logs + // log.Info("Waiting for new block confirmation") + // newHeadA := <-newHeadsA + // blockHashA := newHeadA.Hash() + + // log.Info("Fetching relevant warp logs from the newly produced block") + // logs, err := chainARPCClient.FilterLogs(ctx, interfaces.FilterQuery{ + // BlockHash: &blockHashA, + // Addresses: []common.Address{warp.Module.Address}, + // }) + // Expect(err).Should(BeNil()) + // Expect(len(logs)).Should(Equal(1)) + + // // Check for relevant warp log from subscription and ensure that it matches + // // the log extracted from the last block. + // txLog := logs[0] + // log.Info("Parsing logData as unsigned warp message") + // unsignedMsg, err := avalancheWarp.ParseUnsignedMessage(txLog.Data) + // Expect(err).Should(BeNil()) + + // // Set local variables for the duration of the test + // unsignedWarpMessageID := unsignedMsg.ID() + // unsignedWarpMsg := unsignedMsg + // log.Info("Parsed unsignedWarpMsg", "unsignedWarpMessageID", unsignedWarpMessageID, "unsignedWarpMessage", unsignedWarpMsg) + + // // Loop over each client on chain A to ensure they all have time to accept the block. + // // Note: if we did not confirm this here, the next stage could be racy since it assumes every node + // // has accepted the block. + // for i, uri := range chainANodeURIs { + // chainAWSURI := httpToWebsocketURI(uri, blockchainIDA.String()) + // log.Info("Creating ethclient for blockchainA", "wsURI", chainAWSURI) + // client, err := ethclient.Dial(chainAWSURI) + // Expect(err).Should(BeNil()) + + // // Loop until each node has advanced to >= the height of the block that emitted the warp log + // for { + // block, err := client.BlockByNumber(ctx, nil) + // Expect(err).Should(BeNil()) + // if block.NumberU64() >= newHeadA.Number.Uint64() { + // log.Info("client accepted the block containing SendWarpMessage", "client", i, "height", block.NumberU64()) + // break + // } + // } + // } + + // // Get the aggregate signature for the Warp message + // log.Info("Fetching aggregate signature from the source chain validators") + // warpClient, err := warpBackend.NewWarpClient(chainANodeURIs[0], blockchainIDA.String()) + // Expect(err).Should(BeNil()) + // signedWarpMessageBytes, err := warpClient.GetAggregateSignature(ctx, unsignedWarpMessageID, params.WarpQuorumDenominator) + // Expect(err).Should(BeNil()) + + // // Construct the transaction to send the Warp message to the destination chain + // log.Info("Constructing transaction for the destination chain") + // signedMessage, err := avalancheWarp.ParseMessage(signedWarpMessageBytes) + // Expect(err).Should(BeNil()) + + // numSigners, err := signedMessage.Signature.NumSigners() + // Expect(err).Should(BeNil()) + + // gasLimit, err := teleporter.CalculateReceiveMessageGasLimit(numSigners, teleporterMessage.RequiredGasLimit) + // Expect(err).Should(BeNil()) + + // callData, err := teleporter.EVMTeleporterContractABI.Pack("receiveCrossChainMessage", fundedAddress) + // Expect(err).Should(BeNil()) + + // baseFee, err := chainBRPCClient.EstimateBaseFee(ctx) + // Expect(err).Should(BeNil()) + + // gasTipCap, err := chainBRPCClient.SuggestGasTipCap(ctx) + // Expect(err).Should(BeNil()) + + // nonce, err := chainBRPCClient.NonceAt(ctx, fundedAddress, nil) + // Expect(err).Should(BeNil()) + + // gasFeeCap := baseFee.Mul(baseFee, big.NewInt(2)) + // gasFeeCap.Add(gasFeeCap, big.NewInt(2500000000)) + // destinationTx := predicateutils.NewPredicateTx( + // chainBIDInt, + // nonce, + // &teleporterContractAddress, + // gasLimit, + // gasFeeCap, + // gasTipCap, + // big.NewInt(0), + // callData, + // types.AccessList{}, + // warp.ContractAddress, + // signedMessage.Bytes(), + // ) + + // // Sign and send the transaction on the destination chain + // signer := types.LatestSignerForChainID(chainBIDInt) + // signedTxB, err := types.SignTx(destinationTx, signer, fundedKey) + // Expect(err).Should(BeNil()) + + // log.Info("Subscribing to new heads on destination chain") + // subB, err := chainBWSClient.SubscribeNewHead(ctx, newHeadsB) + // Expect(err).Should(BeNil()) + // defer subB.Unsubscribe() + + // log.Info("Sending transaction to destination chain") + // err = chainBRPCClient.SendTransaction(context.Background(), signedTxB) + // Expect(err).Should(BeNil()) + + // // Sleep to ensure the new block is published to the subscriber + // time.Sleep(5 * time.Second) + // receipt, err := chainBRPCClient.TransactionReceipt(ctx, signedTxB.Hash()) + // Expect(err).Should(BeNil()) + // Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) + // }) + + // ginkgo.It("Receive message on Subnet B", ginkgo.Label("Teleporter", "ReceiveTeleporter"), func() { + // ctx := context.Background() + + // // Get the latest block from Subnet B + // log.Info("Waiting for new block confirmation") + // newHeadB := <-newHeadsB + // log.Info("Received new head", "height", newHeadB.Number.Uint64()) + // blockHashB := newHeadB.Hash() + // block, err := chainBRPCClient.BlockByHash(ctx, blockHashB) + // Expect(err).Should(BeNil()) + // log.Info( + // "Got block", + // "blockHash", blockHashB, + // "blockNumber", block.NumberU64(), + // "transactions", block.Transactions(), + // "numTransactions", len(block.Transactions()), + // "block", block, + // ) + // accessLists := block.Transactions()[0].AccessList() + // Expect(len(accessLists)).Should(Equal(1)) + // Expect(accessLists[0].Address).Should(Equal(warp.Module.Address)) + + // // Check the transaction storage key has warp message we're expecting + // storageKeyHashes := accessLists[0].StorageKeys + // packedPredicate := predicateutils.HashSliceToBytes(storageKeyHashes) + // predicateBytes, err := predicateutils.UnpackPredicate(packedPredicate) + // Expect(err).Should(BeNil()) + // receivedWarpMessage, err = avalancheWarp.ParseMessage(predicateBytes) + // Expect(err).Should(BeNil()) + + // // Check that the transaction has successful receipt status + // txHash := block.Transactions()[0].Hash() + // receipt, err := chainBRPCClient.TransactionReceipt(ctx, txHash) + // Expect(err).Should(BeNil()) + // Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) + + // log.Info("Finished sending warp message, closing down output channel") + + // }) + + // ginkgo.It("Validate Received Warp Message Values", ginkgo.Label("Teleporter", "VerifyWarp"), func() { + // Expect(receivedWarpMessage.SourceChainID).Should(Equal(blockchainIDA)) + // addressedPayload, err := warpPayload.ParseAddressedPayload(receivedWarpMessage.Payload) + // Expect(err).Should(BeNil()) + + // receivedDestinationID, err := ids.ToID(addressedPayload.DestinationChainID.Bytes()) + // Expect(err).Should(BeNil()) + // Expect(receivedDestinationID).Should(Equal(blockchainIDB)) + // Expect(addressedPayload.DestinationAddress).Should(Equal(teleporterContractAddress)) + // Expect(addressedPayload.Payload).Should(Equal(payload)) + + // // Check that the teleporter message is correct + // receivedTeleporterMessage, err := teleporter.UnpackTeleporterMessage(addressedPayload.Payload) + // Expect(err).Should(BeNil()) + // Expect(*receivedTeleporterMessage).Should(Equal(teleporterMessage)) + + // teleporterMessageID = receivedTeleporterMessage.MessageID + // }) + + // ginkgo.It("Check Teleporter Message Received", ginkgo.Label("Teleporter", "TeleporterMessageReceived"), func() { + // data, err := teleporter.PackMessageReceivedMessage(teleporter.MessageReceivedInput{ + // OriginChainID: blockchainIDA, + // MessageID: teleporterMessageID, + // }) + // Expect(err).Should(BeNil()) + // callMessage := interfaces.CallMsg{ + // To: &teleporterContractAddress, + // Data: data, + // } + // result, err := chainBRPCClient.CallContract(context.Background(), callMessage, nil) + // Expect(err).Should(BeNil()) + + // // check the contract call result + // delivered, err := teleporter.UnpackMessageReceivedResult(result) + // Expect(err).Should(BeNil()) + // Expect(delivered).Should(BeTrue()) + // }) + +}) diff --git a/tests/warp-genesis.json b/tests/warp-genesis.json index 71aa30fcb..c5653c7b8 100644 --- a/tests/warp-genesis.json +++ b/tests/warp-genesis.json @@ -28,7 +28,8 @@ "contractNativeMinterConfig": { "blockTimestamp": 0, "adminAddresses": [ - "0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC" + "0x1337cfd2dCff6270615B90938aCB1efE79801704", + "0xAcB633F5B00099c7ec187eB00156c5cd9D854b5B" ] } },