Skip to content

Commit

Permalink
feat(ctp): add solidity tests with foundry (ethereum-optimism#2585)
Browse files Browse the repository at this point in the history
This commits adds solidity tests for contracts-periphery package. More specific
    it covers AssetReceiver, TeleportrWithdrawer & Transactor
  • Loading branch information
tonykogias authored Jun 30, 2022
1 parent 65fa917 commit 9a69357
Show file tree
Hide file tree
Showing 14 changed files with 480 additions and 10 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ packages/contracts/hardhat*
packages/contracts-periphery/coverage*
packages/contracts-periphery/@openzeppelin*
packages/contracts-periphery/hardhat*
packages/contracts-periphery/forge-artifacts*

packages/data-transport-layer/db

Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@
"@eth-optimism/contracts-bedrock/ds-test",
"@eth-optimism/contracts-bedrock/forge-std",
"@eth-optimism/contracts-bedrock/@rari-capital/solmate",
"@eth-optimism/contracts-bedrock/excessively-safe-call"
"@eth-optimism/contracts-bedrock/excessively-safe-call",
"@eth-optimism/contracts-periphery/ds-test",
"@eth-optimism/contracts-periphery/forge-std",
"@eth-optimism/contracts-periphery/@rari-capital/solmate"
]
},
"private": true,
Expand Down
4 changes: 3 additions & 1 deletion packages/contracts-periphery/.depcheckrc
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,7 @@ ignores: [
"eslint-config-prettier",
"eslint-plugin-prettier",
"chai",
"babel-eslint"
"babel-eslint",
"ds-test",
"forge-std"
]
26 changes: 26 additions & 0 deletions packages/contracts-periphery/.gas-snapshot
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
AssetReceiverTest:testFail_withdrawERC20() (gas: 199441)
AssetReceiverTest:testFail_withdrawERC20withAmount() (gas: 199389)
AssetReceiverTest:testFail_withdrawERC721() (gas: 55930)
AssetReceiverTest:testFail_withdrawETH() (gas: 10523)
AssetReceiverTest:testFail_withdrawETHwithAmount() (gas: 10639)
AssetReceiverTest:test_constructor() (gas: 9845)
AssetReceiverTest:test_receive() (gas: 18860)
AssetReceiverTest:test_withdrawERC20() (gas: 183388)
AssetReceiverTest:test_withdrawERC20withAmount() (gas: 182436)
AssetReceiverTest:test_withdrawERC721() (gas: 49149)
AssetReceiverTest:test_withdrawETH() (gas: 26121)
AssetReceiverTest:test_withdrawETHwithAmount() (gas: 26161)
TeleportrWithdrawerTest:testFail_setData() (gas: 8546)
TeleportrWithdrawerTest:testFail_setRecipient() (gas: 9952)
TeleportrWithdrawerTest:testFail_setTeleportr() (gas: 9918)
TeleportrWithdrawerTest:test_constructor() (gas: 9790)
TeleportrWithdrawerTest:test_setData() (gas: 41835)
TeleportrWithdrawerTest:test_setRecipient() (gas: 36176)
TeleportrWithdrawerTest:test_setTeleportr() (gas: 38023)
TeleportrWithdrawerTest:test_withdrawFromTeleportrToContract() (gas: 191517)
TeleportrWithdrawerTest:test_withdrawFromTeleportrToEOA() (gas: 78597)
TransactorTest:testFail_CALL() (gas: 15737)
TransactorTest:testFail_DELEGATECALLL() (gas: 15704)
TransactorTest:test_CALL() (gas: 27132)
TransactorTest:test_DELEGATECALL() (gas: 21266)
TransactorTest:test_constructor() (gas: 9823)
1 change: 1 addition & 0 deletions packages/contracts-periphery/.solcover.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module.exports = {
skipFiles: [
'./test-libraries',
'./foundry-tests'
],
mocha: {
grep: "@skip-on-coverage",
Expand Down
1 change: 1 addition & 0 deletions packages/contracts-periphery/.solhintignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
node_modules
contracts/foundry-tests/*.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
//SPDX-License-Identifier: MIT
pragma solidity 0.8.10;

/* Testing utilities */
import { Test } from "forge-std/Test.sol";
import { TestERC20 } from "../testing/helpers/TestERC20.sol";
import { TestERC721 } from "../testing/helpers/TestERC721.sol";
import { AssetReceiver } from "../universal/AssetReceiver.sol";

contract AssetReceiver_Initializer is Test {
address alice = address(128);
address bob = address(256);

uint8 immutable DEFAULT_TOKEN_ID = 0;

TestERC20 testERC20;
TestERC721 testERC721;
AssetReceiver assetReceiver;

function _setUp() public {
// Deploy ERC20 and ERC721 tokens
testERC20 = new TestERC20();
testERC721 = new TestERC721();

// Deploy AssetReceiver contract
assetReceiver = new AssetReceiver(address(alice));
vm.label(address(assetReceiver), "AssetReceiver");

// Give alice and bob some ETH
vm.deal(alice, 1 ether);
vm.deal(bob, 1 ether);

testERC721.mint(alice, DEFAULT_TOKEN_ID);

vm.label(alice, "alice");
vm.label(bob, "bob");
}
}

contract AssetReceiverTest is AssetReceiver_Initializer {
function setUp() public {
super._setUp();
}

// Tests if the owner was set correctly during deploy
function test_constructor() external {
assertEq(address(alice), assetReceiver.owner());
}

// Tests that receive works as inteded
function test_receive() external {
// Check that contract balance is 0 initially
assertEq(address(assetReceiver).balance, 0);

// Send funds
vm.prank(alice);
(bool success, ) = address(assetReceiver).call{ value: 100 }(hex"");

// Compare balance after the tx sent
assertTrue(success);
assertEq(address(assetReceiver).balance, 100);
}

// Tests withdrawETH function with only an address as argument, called by owner
function test_withdrawETH() external {
// Check contract initial balance
assertEq(address(assetReceiver).balance, 0);
// Fund contract with 1 eth and check caller and contract balances
vm.deal(address(assetReceiver), 1 ether);
assertEq(address(assetReceiver).balance, 1 ether);

assertEq(address(alice).balance, 1 ether);

// call withdrawETH
vm.prank(alice);
assetReceiver.withdrawETH(payable(alice));

// check balances after the call
assertEq(address(assetReceiver).balance, 0);
assertEq(address(alice).balance, 2 ether);
}

// withdrawETH should fail if called by non-owner
function testFail_withdrawETH() external {
vm.deal(address(assetReceiver), 1 ether);
assetReceiver.withdrawETH(payable(alice));
vm.expectRevert("UNAUTHORIZED");
}

// Similar as withdrawETH but specify amount to withdraw
function test_withdrawETHwithAmount() external {
assertEq(address(assetReceiver).balance, 0);

vm.deal(address(assetReceiver), 1 ether);
assertEq(address(assetReceiver).balance, 1 ether);

assertEq(address(alice).balance, 1 ether);

// call withdrawETH
vm.prank(alice);
assetReceiver.withdrawETH(payable(alice), 0.5 ether);

// check balances after the call
assertEq(address(assetReceiver).balance, 0.5 ether);
assertEq(address(alice).balance, 1.5 ether);
}

// withdrawETH with address and amount as arguments called by non-owner
function testFail_withdrawETHwithAmount() external {
vm.deal(address(assetReceiver), 1 ether);
assetReceiver.withdrawETH(payable(alice), 0.5 ether);
vm.expectRevert("UNAUTHORIZED");
}

// Test withdrawERC20 with token and address arguments, from owner
function test_withdrawERC20() external {
// check balances before the call
assertEq(testERC20.balanceOf(address(assetReceiver)), 0);

deal(address(testERC20), address(assetReceiver), 100_000);
assertEq(testERC20.balanceOf(address(assetReceiver)), 100_000);
assertEq(testERC20.balanceOf(alice), 0);

// call withdrawERC20
vm.prank(alice);
assetReceiver.withdrawERC20(testERC20, alice);

// check balances after the call
assertEq(testERC20.balanceOf(alice), 100_000);
assertEq(testERC20.balanceOf(address(assetReceiver)), 0);
}

// Same as withdrawERC20 but call from non-owner
function testFail_withdrawERC20() external {
deal(address(testERC20), address(assetReceiver), 100_000);
assetReceiver.withdrawERC20(testERC20, alice);
vm.expectRevert("UNAUTHORIZED");
}

// Similar as withdrawERC20 but specify amount to withdraw
function test_withdrawERC20withAmount() external {
// check balances before the call
assertEq(testERC20.balanceOf(address(assetReceiver)), 0);

deal(address(testERC20), address(assetReceiver), 100_000);
assertEq(testERC20.balanceOf(address(assetReceiver)), 100_000);
assertEq(testERC20.balanceOf(alice), 0);

// call withdrawERC20
vm.prank(alice);
assetReceiver.withdrawERC20(testERC20, alice, 50_000);

// check balances after the call
assertEq(testERC20.balanceOf(alice), 50_000);
assertEq(testERC20.balanceOf(address(assetReceiver)), 50_000);
}

// Similar as withdrawERC20 with amount but call from non-owner
function testFail_withdrawERC20withAmount() external {
deal(address(testERC20), address(assetReceiver), 100_000);
assetReceiver.withdrawERC20(testERC20, alice, 50_000);
vm.expectRevert("UNAUTHORIZED");
}

// Test withdrawERC721 from owner
function test_withdrawERC721() external {
// Check owner of the token before calling withdrawERC721
assertEq(testERC721.ownerOf(DEFAULT_TOKEN_ID), alice);

// Send the token from alice to the contract
vm.prank(alice);
testERC721.transferFrom(alice, address(assetReceiver), DEFAULT_TOKEN_ID);
assertEq(testERC721.ownerOf(DEFAULT_TOKEN_ID), address(assetReceiver));

// Call withdrawERC721
vm.prank(alice);
assetReceiver.withdrawERC721(testERC721, alice, DEFAULT_TOKEN_ID);

// Check the owner after the call
assertEq(testERC721.ownerOf(DEFAULT_TOKEN_ID), alice);
}

// Similar as withdrawERC721 but call from non-owner
function testFail_withdrawERC721() external {
vm.prank(alice);
testERC721.transferFrom(alice, address(assetReceiver), DEFAULT_TOKEN_ID);
assetReceiver.withdrawERC721(testERC721, alice, DEFAULT_TOKEN_ID);
vm.expectRevert("UNAUTHORIZED");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
//SPDX-License-Identifier: MIT
pragma solidity 0.8.10;

/* Testing utilities */
import { Test } from "forge-std/Test.sol";
import { SimpleStorage } from "../testing/helpers/SimpleStorage.sol";
import { MockTeleportr } from "../testing/helpers/MockTeleportr.sol";
import { TeleportrWithdrawer } from "../universal/TeleportrWithdrawer.sol";

contract TeleportrWithdrawer_Initializer is Test {
address alice = address(128);
address bob = address(256);

TeleportrWithdrawer teleportrWithdrawer;
MockTeleportr mockTeleportr;
SimpleStorage simpleStorage;

function _setUp() public {
// Deploy MockTeleportr and SimpleStorage helper contracts
mockTeleportr = new MockTeleportr();
simpleStorage = new SimpleStorage();

// Deploy Transactor contract
teleportrWithdrawer = new TeleportrWithdrawer(address(alice));
vm.label(address(teleportrWithdrawer), "TeleportrWithdrawer");

// Give alice and bob some ETH
vm.deal(alice, 1 ether);
vm.deal(bob, 1 ether);

vm.label(alice, "alice");
vm.label(bob, "bob");
}
}

contract TeleportrWithdrawerTest is TeleportrWithdrawer_Initializer {
function setUp() public {
super._setUp();
}

// Tests if the owner was set correctly during deploy
function test_constructor() external {
assertEq(address(alice), teleportrWithdrawer.owner());
}

// Tests setRecipient function when called by authorized address
function test_setRecipient() external {
// Call setRecipient from alice
vm.prank(alice);
teleportrWithdrawer.setRecipient(address(alice));
assertEq(teleportrWithdrawer.recipient(), address(alice));
}

// setRecipient should fail if called by unauthorized address
function testFail_setRecipient() external {
teleportrWithdrawer.setRecipient(address(alice));
vm.expectRevert("UNAUTHORIZED");
}

// Tests setTeleportr function when called by authorized address
function test_setTeleportr() external {
// Call setRecipient from alice
vm.prank(alice);
teleportrWithdrawer.setTeleportr(address(mockTeleportr));
assertEq(teleportrWithdrawer.teleportr(), address(mockTeleportr));
}

// setTeleportr should fail if called by unauthorized address
function testFail_setTeleportr() external {
teleportrWithdrawer.setTeleportr(address(bob));
vm.expectRevert("UNAUTHORIZED");
}

// Tests setData function when called by authorized address
function test_setData() external {
bytes memory data = "0x1234567890";
// Call setData from alice
vm.prank(alice);
teleportrWithdrawer.setData(data);
assertEq(teleportrWithdrawer.data(), data);
}

// setData should fail if called by unauthorized address
function testFail_setData() external {
bytes memory data = "0x1234567890";
teleportrWithdrawer.setData(data);
vm.expectRevert("UNAUTHORIZED");
}

// Tests withdrawFromTeleportr, when called expected to withdraw the balance
// to the recipient address when the target is an EOA
function test_withdrawFromTeleportrToEOA() external {
// Fund the Teleportr contract with 1 ETH
vm.deal(address(teleportrWithdrawer), 1 ether);
// Set target address and Teleportr
vm.startPrank(alice);
teleportrWithdrawer.setRecipient(address(bob));
teleportrWithdrawer.setTeleportr(address(mockTeleportr));
vm.stopPrank();
// Run withdrawFromTeleportr
assertEq(address(bob).balance, 1 ether);
teleportrWithdrawer.withdrawFromTeleportr();
assertEq(address(bob).balance, 2 ether);
}

// When called from a contract account it should withdraw the balance and trigger the code
function test_withdrawFromTeleportrToContract() external {
bytes32 key = 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa;
bytes32 value = 0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb;
bytes memory data = abi.encodeWithSelector(simpleStorage.set.selector, key, value);
// Fund the Teleportr contract with 1 ETH
vm.deal(address(teleportrWithdrawer), 1 ether);
// Set target address and Teleportr
vm.startPrank(alice);
teleportrWithdrawer.setRecipient(address(simpleStorage));
teleportrWithdrawer.setTeleportr(address(mockTeleportr));
teleportrWithdrawer.setData(data);
vm.stopPrank();
// Run withdrawFromTeleportr
assertEq(address(simpleStorage).balance, 0);
teleportrWithdrawer.withdrawFromTeleportr();
assertEq(address(simpleStorage).balance, 1 ether);
assertEq(simpleStorage.get(key), value);
}
}
Loading

0 comments on commit 9a69357

Please sign in to comment.