diff --git a/contracts/Bridge/BaseXVSProxyOFT.sol b/contracts/Bridge/BaseXVSProxyOFT.sol index 730675e..221ef53 100644 --- a/contracts/Bridge/BaseXVSProxyOFT.sol +++ b/contracts/Bridge/BaseXVSProxyOFT.sol @@ -23,6 +23,7 @@ abstract contract BaseXVSProxyOFT is Pausable, ExponentialNoError, BaseOFTV2 { using SafeERC20 for IERC20; IERC20 internal immutable innerToken; uint256 internal immutable ld2sdRate; + bool public sendAndCallEnabled; /** * @notice The address of ResilientOracle contract wrapped in its interface. @@ -238,6 +239,42 @@ abstract contract BaseXVSProxyOFT is Pausable, ExponentialNoError, BaseOFTV2 { emit TrustedRemoteRemoved(remoteChainId_); } + /** + * @notice It enables or disables sendAndCall functionality for the bridge. + * @param enabled_ Boolean indicating whether the sendAndCall function should be enabled or disabled. + */ + function updateSendAndCallEnabled(bool enabled_) external onlyOwner { + sendAndCallEnabled = enabled_; + } + + /** + * @notice Initiates a cross-chain token transfer and triggers a call on the destination chain. + * @dev This internal override function enables the contract to send tokens and invoke calls on the specified + * destination chain. It checks whether the sendAndCall feature is enabled before proceeding with the transfer. + * @param from_ Address from which tokens will be debited. + * @param dstChainId_ Destination chain id on which tokens will be send. + * @param toAddress_ Address on which tokens will be credited on destination chain. + * @param amount_ Amount of tokens that will be transferred. + * @param payload_ Additional data payload for the call on the destination chain. + * @param dstGasForCall_ The amount of gas allocated for the call on the destination chain. + * @param callparams_ Additional parameters, including refund address, ZRO payment address, + * and adapter params. + */ + + function sendAndCall( + address from_, + uint16 dstChainId_, + bytes32 toAddress_, + uint256 amount_, + bytes calldata payload_, + uint64 dstGasForCall_, + LzCallParams calldata callparams_ + ) public payable override { + require(sendAndCallEnabled, "sendAndCall is disabled"); + + super.sendAndCall(from_, dstChainId_, toAddress_, amount_, payload_, dstGasForCall_, callparams_); + } + /** * @notice Empty implementation of renounce ownership to avoid any mishappening. */ diff --git a/test/proxyOFT.ts b/test/proxyOFT.ts index c9752c0..cd46d1e 100644 --- a/test/proxyOFT.ts +++ b/test/proxyOFT.ts @@ -132,6 +132,7 @@ describe("Proxy OFTV2: ", function () { "setPayloadSizeLimit(uint16,uint256)", "setUseCustomAdapterParams(bool)", "removeTrustedRemote(uint16)", + "updateSendAndCallEnabled(bool)", ]; const activeArray = new Array(functionregistry.length).fill(true); await bridgeAdminRemote.upsertSignature(functionregistry, activeArray); @@ -718,4 +719,65 @@ describe("Proxy OFTV2: ", function () { }); expect(await localOFT.trustedRemoteLookup(remoteChainId)).equals("0x"); }); + it("Reverts when sendAndCall is disabled", async function () { + const amount = ethers.utils.parseEther("2", 18); + const dstGasForCall_ = 0; + const uint160Value = BigInt("0x" + acc3.address.slice(2)); + const bytes32Value = uint160Value << BigInt(96); + const acc3AddressBytes32 = "0x" + bytes32Value.toString(16).padStart(32, "0"); + + await expect( + localOFT + .connect(acc1) + .sendAndCall(acc3.address, remoteChainId, acc3AddressBytes32, amount, "0x", dstGasForCall_, [ + acc1.address, + acc1.address, + "0x", + ]), + ).to.be.revertedWith("sendAndCall is disabled"); + }); + + it("Successfully call sendAndCall", async function () { + const uint160Value = BigInt("0x" + acc3.address.slice(2)); + const bytes32Value = uint160Value << BigInt(96); + const acc3AddressBytes32 = "0x" + bytes32Value.toString(16).padStart(32, "0"); + let data = localOFT.interface.encodeFunctionData("setMinDstGas", [remoteChainId, 1, 300000]); + const amount = ethers.utils.parseEther("2", 18); + const dstGasForCall_ = 0; + await acc1.sendTransaction({ + to: bridgeAdminLocal.address, + data: data, + }); + + await acc1.sendTransaction({ + to: bridgeAdminLocal.address, + data: data, + }); + data = localOFT.interface.encodeFunctionData("updateSendAndCallEnabled", [true]); + await acc1.sendTransaction({ + to: bridgeAdminLocal.address, + data: data, + }); + + const adapterParams = ethers.utils.solidityPack(["uint16", "uint256"], [1, 300000]); + await localToken.connect(acc1).faucet(amount); + await localToken.connect(acc1).approve(localOFT.address, amount); + expect(await localOFT.sendAndCallEnabled()).to.be.true; + + const nativeFee = (await localOFT.estimateSendFee(remoteChainId, acc3AddressBytes32, amount, false, adapterParams)) + .nativeFee; + + await localOFT + .connect(acc1) + .sendAndCall( + acc1.address, + remoteChainId, + acc3AddressBytes32, + amount, + "0x", + dstGasForCall_, + [acc1.address, acc1.address, adapterParams], + { value: nativeFee }, + ); + }); });