Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🗞️ Allow expected reverts in verifyAndExecutePackets and fix forge coverage in TestHelper #615

Closed
wants to merge 12 commits into from
4 changes: 2 additions & 2 deletions examples/oft/test/foundry/MyOFT.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ contract MyOFTTest is TestHelperOz5 {

vm.prank(userA);
aOFT.send{ value: fee.nativeFee }(sendParam, fee, payable(address(this)));
verifyPackets(bEid, addressToBytes32(address(bOFT)));
verifyAndExecutePackets(bEid, addressToBytes32(address(bOFT)));

assertEq(aOFT.balanceOf(userA), initialBalance - tokensToSend);
assertEq(bOFT.balanceOf(userB), initialBalance + tokensToSend);
Expand Down Expand Up @@ -130,7 +130,7 @@ contract MyOFTTest is TestHelperOz5 {
fee,
payable(address(this))
);
verifyPackets(bEid, addressToBytes32(address(bOFT)));
verifyAndExecutePackets(bEid, addressToBytes32(address(bOFT)));

// lzCompose params
uint32 dstEid_ = bEid;
Expand Down
4 changes: 3 additions & 1 deletion packages/test-devtools-evm-foundry/.eslintignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.turbo
dist
node_modules
node_modules
out
cache
8 changes: 8 additions & 0 deletions packages/test-devtools-evm-foundry/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
cache
# Ignore everything in the artifacts folder
artifacts/*

# Do not ignore the directory structure up to the specific file
!artifacts/
!artifacts/TestHelperOz5.sol/
!artifacts/TestHelperOz5.sol/TestHelperOz5.json
28,264 changes: 28,222 additions & 42 deletions packages/test-devtools-evm-foundry/artifacts/TestHelperOz5.sol/TestHelperOz5.json

Large diffs are not rendered by default.

327 changes: 206 additions & 121 deletions packages/test-devtools-evm-foundry/contracts/TestHelperOz5.sol

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/test-devtools-evm-foundry/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ remappings = [
# To remedy this, we'll remap the ds-test and forge-std imports to ou own versions
'ds-test/=node_modules/@layerzerolabs/toolbox-foundry/src/ds-test/src/',
'forge-std/=node_modules/@layerzerolabs/toolbox-foundry/src/forge-std/src/',
'solidity-bytes-utils/=node_modules/@layerzerolabs/toolbox-foundry/lib/solidity-bytes-utils/',
'@layerzerolabs/=node_modules/@layerzerolabs/',
'@openzeppelin/=node_modules/@openzeppelin/',
]
3 changes: 2 additions & 1 deletion packages/test-devtools-evm-foundry/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
],
"scripts": {
"lint": "$npm_execpath eslint '**/*.{js,ts,json}'",
"lint:fix": "eslint --fix '**/*.{js,ts,json}'"
"lint:fix": "eslint --fix '**/*.{js,ts,json}'",
"test": "forge test"
},
"devDependencies": {
"@layerzerolabs/lz-evm-messagelib-v2": "^2.3.3",
Expand Down
110 changes: 110 additions & 0 deletions packages/test-devtools-evm-foundry/test/MyOFT.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

// Mock imports
import { OFTMock } from "./mocks/OFTMock.sol";

// OApp imports
import { IOAppOptionsType3, EnforcedOptionParam } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/libs/OAppOptionsType3.sol";
import { OptionsBuilder } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/libs/OptionsBuilder.sol";

// OFT imports
import { IOFT, SendParam, OFTReceipt } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oft/interfaces/IOFT.sol";
import { MessagingFee, MessagingReceipt } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oft/OFTCore.sol";

// OZ imports
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";

// Forge imports
import "forge-std/console.sol";

// DevTools imports
import { TestHelperOz5 } from "../contracts/TestHelperOz5.sol";

contract MyOFTTest is TestHelperOz5 {
using OptionsBuilder for bytes;

uint32 aEid = 1;
uint32 bEid = 2;

OFTMock aOFT;
OFTMock bOFT;

address public userA = address(0x1);
address public userB = address(0x2);
uint256 public initialBalance = 100 ether;

function setUp() public virtual override {
vm.deal(userA, 1000 ether);
vm.deal(userB, 1000 ether);

super.setUp();
setUpEndpoints(2, LibraryType.UltraLightNode);

aOFT = OFTMock(
_deployOApp(type(OFTMock).creationCode, abi.encode("aOFT", "aOFT", address(endpoints[aEid]), address(this)))
);

bOFT = OFTMock(
_deployOApp(type(OFTMock).creationCode, abi.encode("bOFT", "bOFT", address(endpoints[bEid]), address(this)))
);

// config and wire the ofts
address[] memory ofts = new address[](2);
ofts[0] = address(aOFT);
ofts[1] = address(bOFT);
this.wireOApps(ofts);

// mint tokens
aOFT.mint(userA, initialBalance);
bOFT.mint(userB, initialBalance);
}

function test_verify_execute_receive_simple_send_works() public {
uint256 tokensToSend = 9 ether;
bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(200000, 0);
SendParam memory sendParam = SendParam(
bEid,
addressToBytes32(userB),
tokensToSend,
tokensToSend,
options,
"",
""
);
MessagingFee memory fee = aOFT.quoteSend(sendParam, false);

assertEq(aOFT.balanceOf(userA), initialBalance);
assertEq(bOFT.balanceOf(userB), initialBalance);

vm.prank(userA);
aOFT.send{ value: fee.nativeFee }(sendParam, fee, payable(address(this)));

// should revert if more than 10 ether is sent
verifyAndExecutePackets(bEid, addressToBytes32(address(bOFT)), 1, address(0), bytes(""), bytes(""));
}

function test_verify_execute_receive_revert() public {
uint256 tokensToSend = 11 ether;
bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(200000, 0);
SendParam memory sendParam = SendParam(
bEid,
addressToBytes32(userB),
tokensToSend,
tokensToSend,
options,
"",
""
);
MessagingFee memory fee = aOFT.quoteSend(sendParam, false);

assertEq(aOFT.balanceOf(userA), initialBalance);
assertEq(bOFT.balanceOf(userB), initialBalance);

vm.prank(userA);
aOFT.send{ value: fee.nativeFee }(sendParam, fee, payable(address(this)));

// should revert if more than 10 ether is sent
verifyAndExecutePackets(bEid, addressToBytes32(address(bOFT)), 1, address(0), abi.encodePacked(OFTMock.DummyLzReceiveError.selector, tokensToSend), bytes(""));
}
}
71 changes: 71 additions & 0 deletions packages/test-devtools-evm-foundry/test/OmniCounter.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.15;

import { Packet } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ISendLib.sol";
import { PacketV1Codec } from "@layerzerolabs/lz-evm-protocol-v2/contracts/messagelib/libs/PacketV1Codec.sol";
import { Errors } from "@layerzerolabs/lz-evm-protocol-v2/contracts/libs/Errors.sol";

import { OptionsBuilder } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/libs/OptionsBuilder.sol";

import { OmniCounter, MsgCodec } from "./mocks/OmniCounter.sol";

import { TestHelperOz5 } from "../contracts/TestHelperOz5.sol";

import "forge-std/console.sol";

contract OmniCounterTest is TestHelperOz5 {
using OptionsBuilder for bytes;

uint32 aEid = 1;
uint32 bEid = 2;

// omnicounter with precrime
OmniCounter aCounter;
OmniCounter bCounter;

address offchain = address(0xDEAD);

error CrimeFound(bytes crime);

function setUp() public virtual override {
super.setUp();

setUpEndpoints(2, LibraryType.UltraLightNode);

address[] memory uas = setupOApps(type(OmniCounter).creationCode, 1, 2);
aCounter = OmniCounter(payable(uas[0]));
bCounter = OmniCounter(payable(uas[1]));
}

function test_lzCompose_increment() public {
uint256 countBefore = bCounter.count();
uint256 composedCountBefore = bCounter.composedCount();

bytes memory options = OptionsBuilder
.newOptions()
.addExecutorLzReceiveOption(200000, 0)
.addExecutorLzComposeOption(0, 200000, 0);
(uint256 nativeFee, ) = aCounter.quote(bEid, MsgCodec.COMPOSED_TYPE, options);
aCounter.increment{ value: nativeFee }(bEid, MsgCodec.COMPOSED_TYPE, options);

verifyAndExecutePackets(bEid, addressToBytes32(address(bCounter)), 0, address(bCounter));

assertEq(bCounter.count(), countBefore + 1, "increment B1 assertion failure");
assertEq(bCounter.composedCount(), composedCountBefore + 1, "increment B2 assertion failure");
}

function test_verify_execute_compose_revert() public {
uint256 countBefore = bCounter.count();
uint256 composedCountBefore = bCounter.composedCount();

bytes memory options = OptionsBuilder
.newOptions()
.addExecutorLzReceiveOption(200000, 0)
.addExecutorLzComposeOption(0, 200000, 0);
(uint256 nativeFee, ) = aCounter.quote(bEid, MsgCodec.COMPOSED_ABA_TYPE, options);
aCounter.increment{ value: nativeFee }(bEid, MsgCodec.COMPOSED_ABA_TYPE, options);

verifyAndExecutePackets(bEid, addressToBytes32(address(bCounter)), 0, address(bCounter), bytes(""), bytes("not implemented"));
}
}
35 changes: 35 additions & 0 deletions packages/test-devtools-evm-foundry/test/mocks/OFTComposerMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import { IOAppComposer } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/interfaces/IOAppComposer.sol";

contract OFTComposerMock is IOAppComposer {
// default empty values for testing a lzCompose received message
address public from;
bytes32 public guid;
bytes public message;
address public executor;
bytes public extraData;

error DummyComposerError(bytes32 guid);

function compareStrings(bytes memory a, bytes memory b) public view returns (bool) {
return (keccak256(abi.encodePacked((a))) == keccak256(abi.encodePacked((b))));
}

function lzCompose(
address _from,
bytes32 _guid,
bytes calldata _message,
address _executor,
bytes calldata /*_extraData*/
) external payable {
from = _from;
guid = _guid;
message = _message;
executor = _executor;
extraData = _message;

revert DummyComposerError(_guid);
}
}
76 changes: 76 additions & 0 deletions packages/test-devtools-evm-foundry/test/mocks/OFTMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { OFT } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oft/OFT.sol";
import { SendParam } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oft/OFTCore.sol";

contract OFTMock is OFT {
error DummyLzReceiveError(uint256 actualAmount);

constructor(
string memory _name,
string memory _symbol,
address _lzEndpoint,
address _delegate
) Ownable(_delegate) OFT(_name, _symbol, _lzEndpoint, _delegate) {}

function mint(address _to, uint256 _amount) public {
_mint(_to, _amount);
}

function _credit(
address _to,
uint256 _amountLD,
uint32 /*_srcEid*/
) internal virtual override returns (uint256 amountReceivedLD) {
if (_amountLD > 10 ether) {
revert DummyLzReceiveError(_amountLD);
}

// @dev Default OFT mints on dst.
_mint(_to, _amountLD);
// @dev In the case of NON-default OFT, the _amountLD MIGHT not be == amountReceivedLD.
return _amountLD;
}

// @dev expose internal functions for testing purposes
function debit(
uint256 _amountToSendLD,
uint256 _minAmountToCreditLD,
uint32 _dstEid
) public returns (uint256 amountDebitedLD, uint256 amountToCreditLD) {
return _debit(msg.sender, _amountToSendLD, _minAmountToCreditLD, _dstEid);
}

function debitView(
uint256 _amountToSendLD,
uint256 _minAmountToCreditLD,
uint32 _dstEid
) public view returns (uint256 amountDebitedLD, uint256 amountToCreditLD) {
return _debitView(_amountToSendLD, _minAmountToCreditLD, _dstEid);
}

function removeDust(uint256 _amountLD) public view returns (uint256 amountLD) {
return _removeDust(_amountLD);
}

function toLD(uint64 _amountSD) public view returns (uint256 amountLD) {
return _toLD(_amountSD);
}

function toSD(uint256 _amountLD) public view returns (uint64 amountSD) {
return _toSD(_amountLD);
}

function credit(address _to, uint256 _amountToCreditLD, uint32 _srcEid) public returns (uint256 amountReceivedLD) {
return _credit(_to, _amountToCreditLD, _srcEid);
}

function buildMsgAndOptions(
SendParam calldata _sendParam,
uint256 _amountToCreditLD
) public view returns (bytes memory message, bytes memory options) {
return _buildMsgAndOptions(_sendParam, _amountToCreditLD);
}
}
Loading