Skip to content

Instantly share code, notes, and snippets.

@0xmp
Last active May 26, 2024 15:40
Show Gist options
  • Save 0xmp/4da10ab59c04f71cdb9557ff320f9fc2 to your computer and use it in GitHub Desktop.
Save 0xmp/4da10ab59c04f71cdb9557ff320f9fc2 to your computer and use it in GitHub Desktop.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import "forge-std/src/Test.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import "../../contracts/bridge/Bridge.sol";
import "../../contracts/bridge/QuotaManager.sol";
// `forge test --mt test_MPBridgeCallGas -vvvv --fork-url 127.0.0.1:8545 --isolate`
// From the traces, we can see that onMessageInvocation runs out of gas, consuming "only" 2991360 instead of the intended 3M gas
contract DummyTarget {
function onMessageInvocation(bytes calldata _data) external payable {
uint256 gasBefore = gasleft();
uint256 toConsume = abi.decode(_data, (uint256));
while (gasBefore - gasleft() < toConsume - 200) {} // Waste up to `toConsume - 200` gas
}
receive() external payable {}
}
contract TestBridge is Test {
using LibMath for uint256;
Bridge public bridge;
QuotaManager public quotaManager;
DummyTarget public dummyTarget;
function setUp() public {
bridge = new Bridge();
ERC1967Proxy proxy = new ERC1967Proxy(address(bridge), abi.encodeCall(Bridge.init, (address(this), address(this))));
bridge = Bridge(payable(address(proxy)));
quotaManager = new QuotaManager();
proxy = new ERC1967Proxy(address(quotaManager), abi.encodeCall(QuotaManager.init, (address(this), address(this), 24 * 3600)));
quotaManager = QuotaManager(address(proxy));
quotaManager.updateQuota(address(0), 1000 ether);
dummyTarget = new DummyTarget();
vm.label(address(bridge), "Bridge");
vm.label(address(quotaManager), "QuotaManager");
vm.label(address(dummyTarget), "DummyTarget");
vm.deal(address(bridge), 100 ether);
vm.deal(address(this), 1 ether);
}
function test_MPBridgeCallGas() public {
uint24 gasInnerCall = 3e6; // 3M gas required in tx
bytes memory data = abi.encodeCall(DummyTarget.onMessageInvocation, (abi.encode(gasInnerCall, "")));
console2.log("Gas to be consumed in inner call is %s", gasInnerCall);
IBridge.Message memory message = IBridge.Message({
id: 0,
fee: 1 wei,
gasLimit: uint32(gasInnerCall + 800_000 + (data.length + 256) / 16),
from: address(32),
srcChainId: 2,
srcOwner: address(48),
destChainId: 1,
destOwner: address(dummyTarget), // Not address(this) otherwise msg.gasLimit == gasleft()
to: address(dummyTarget),
value: 1 wei,
data: data
});
bridge.processMessage{gas: gasInnerCall + 155_000}(message, ""); // 155_000 has been chosen so that 63/64 * gasleft() is <= msg.gasLimit in external call
}
function getAddress(uint64 _chainId, bytes32 _name) external view returns (address) { // this is AddressManager for simplicity
if (_name == bytes32("signal_service") && _chainId == block.chainid) return address(this);
if (_name == bytes32("signal_service")) return address(17);
else if (_name == bytes32("bridge") && _chainId == block.chainid) return address(bridge);
else if (_name == bytes32("bridge")) return address(34);
else if (_name == bytes32("quota_manager") && _chainId == block.chainid) return address(quotaManager);
else if (_name == bytes32("erc20_vault") && _chainId == block.chainid) return address(51);
else console2.log("Unknown resolved address: %s", string(abi.encodePacked(_name)));
revert("Get address failed.");
}
function proveSignalReceived( // this is SignalService for simplicity
uint64 _chainId,
address _app,
bytes32 _signal,
bytes calldata _proof
)
external
virtual
returns (uint256 numCacheOps_)
{
return 0;
}
receive() external payable {} // To receive the fee for processing the message
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment