forked from AztecProtocol/aztec-connect-bridges
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
269a6e4
commit dc9c463
Showing
2 changed files
with
228 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// Copyright 2022 Aztec. | ||
pragma solidity >=0.8.4; | ||
|
||
import {BridgeTestBase} from "./../../aztec/base/BridgeTestBase.sol"; | ||
import {AztecTypes} from "rollup-encoder/libraries/AztecTypes.sol"; | ||
|
||
// Example-specific imports | ||
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
import {ExampleBridge} from "../../../bridges/example/ExampleBridge.sol"; | ||
import {ErrorLib} from "../../../bridges/base/ErrorLib.sol"; | ||
|
||
/** | ||
* @notice The purpose of this test is to test the bridge in an environment that is as close to the final deployment | ||
* as possible without spinning up all the rollup infrastructure (sequencer, proof generator etc.). | ||
*/ | ||
contract ExampleE2ETest is BridgeTestBase { | ||
address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; | ||
address private constant BENEFICIARY = address(11); | ||
|
||
// The reference to the example bridge | ||
ExampleBridge internal bridge; | ||
// To store the id of the example bridge after being added | ||
uint256 private id; | ||
|
||
function setUp() public { | ||
// Deploy a new example bridge | ||
bridge = new ExampleBridge(address(ROLLUP_PROCESSOR)); | ||
|
||
// Use the label cheatcode to mark the address with "Example Bridge" in the traces | ||
vm.label(address(bridge), "Example Bridge"); | ||
|
||
// Impersonate the multi-sig to add a new bridge | ||
vm.startPrank(MULTI_SIG); | ||
|
||
// List the example-bridge with a gasLimit of 120k | ||
// WARNING: If you set this value too low the interaction will fail for seemingly no reason! | ||
// OTOH if you se it too high bridge users will pay too much | ||
ROLLUP_PROCESSOR.setSupportedBridge(address(bridge), 120000); | ||
|
||
// List USDC with a gasLimit of 100k | ||
// Note: necessary for assets which are not already registered on RollupProcessor | ||
// Call https://etherscan.io/address/0xFF1F2B4ADb9dF6FC8eAFecDcbF96A2B351680455#readProxyContract#F25 to get | ||
// addresses of all the listed ERC20 tokens | ||
ROLLUP_PROCESSOR.setSupportedAsset(USDC, 100000); | ||
|
||
vm.stopPrank(); | ||
|
||
// Fetch the id of the example bridge | ||
id = ROLLUP_PROCESSOR.getSupportedBridgesLength(); | ||
|
||
// Subsidize the bridge when used with USDC and register a beneficiary | ||
AztecTypes.AztecAsset memory usdcAsset = ROLLUP_ENCODER.getRealAztecAsset(USDC); | ||
uint256 criteria = bridge.computeCriteria(usdcAsset, emptyAsset, usdcAsset, emptyAsset, 0); | ||
uint32 gasPerMinute = 200; | ||
SUBSIDY.subsidize{value: 1 ether}(address(bridge), criteria, gasPerMinute); | ||
|
||
SUBSIDY.registerBeneficiary(BENEFICIARY); | ||
|
||
// Set the rollupBeneficiary on BridgeTestBase so that it gets included in the proofData | ||
ROLLUP_ENCODER.setRollupBeneficiary(BENEFICIARY); | ||
} | ||
|
||
// @dev In order to avoid overflows we set _depositAmount to be uint96 instead of uint256. | ||
function testExampleBridgeE2ETest(uint96 _depositAmount) public { | ||
vm.assume(_depositAmount > 1); | ||
vm.warp(block.timestamp + 1 days); | ||
|
||
// Use the helper function to fetch the support AztecAsset for DAI | ||
AztecTypes.AztecAsset memory usdcAsset = ROLLUP_ENCODER.getRealAztecAsset(address(USDC)); | ||
|
||
// Mint the depositAmount of Dai to rollupProcessor | ||
deal(USDC, address(ROLLUP_PROCESSOR), _depositAmount); | ||
|
||
// Computes the encoded data for the specific bridge interaction | ||
ROLLUP_ENCODER.defiInteractionL2(id, usdcAsset, emptyAsset, usdcAsset, emptyAsset, 0, _depositAmount); | ||
|
||
// Execute the rollup with the bridge interaction. Ensure that event as seen above is emitted. | ||
(uint256 outputValueA, uint256 outputValueB, bool isAsync) = ROLLUP_ENCODER.processRollupAndGetBridgeResult(); | ||
|
||
// Note: Unlike in unit tests there is no need to manually transfer the tokens - RollupProcessor does this | ||
|
||
// Check the output values are as expected | ||
assertEq(outputValueA, _depositAmount, "outputValueA doesn't equal deposit"); | ||
assertEq(outputValueB, 0, "Non-zero outputValueB"); | ||
assertFalse(isAsync, "Bridge is not synchronous"); | ||
|
||
// Check that the balance of the rollup is same as before interaction (bridge just sends funds back) | ||
assertEq(_depositAmount, IERC20(USDC).balanceOf(address(ROLLUP_PROCESSOR)), "Balances must match"); | ||
|
||
// Perform a second rollup with half the deposit, perform similar checks. | ||
uint256 secondDeposit = _depositAmount / 2; | ||
|
||
ROLLUP_ENCODER.defiInteractionL2(id, usdcAsset, emptyAsset, usdcAsset, emptyAsset, 0, secondDeposit); | ||
|
||
// Execute the rollup with the bridge interaction. Ensure that event as seen above is emitted. | ||
(outputValueA, outputValueB, isAsync) = ROLLUP_ENCODER.processRollupAndGetBridgeResult(); | ||
|
||
// Check the output values are as expected | ||
assertEq(outputValueA, secondDeposit, "outputValueA doesn't equal second deposit"); | ||
assertEq(outputValueB, 0, "Non-zero outputValueB"); | ||
assertFalse(isAsync, "Bridge is not synchronous"); | ||
|
||
// Check that the balance of the rollup is same as before interaction (bridge just sends funds back) | ||
assertEq(_depositAmount, IERC20(USDC).balanceOf(address(ROLLUP_PROCESSOR)), "Balances must match"); | ||
|
||
assertGt(SUBSIDY.claimableAmount(BENEFICIARY), 0, "Claimable was not updated"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// Copyright 2022 Aztec. | ||
pragma solidity >=0.8.4; | ||
|
||
import {BridgeTestBase} from "./../../aztec/base/BridgeTestBase.sol"; | ||
import {AztecTypes} from "rollup-encoder/libraries/AztecTypes.sol"; | ||
|
||
// Example-specific imports | ||
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
import {ExampleBridge} from "../../../bridges/example/ExampleBridge.sol"; | ||
import {ErrorLib} from "../../../bridges/base/ErrorLib.sol"; | ||
|
||
// @notice The purpose of this test is to directly test convert functionality of the bridge. | ||
contract ExampleUnitTest is BridgeTestBase { | ||
address private constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; | ||
address private constant BENEFICIARY = address(11); | ||
|
||
address private rollupProcessor; | ||
// The reference to the example bridge | ||
ExampleBridge private bridge; | ||
|
||
// @dev This method exists on RollupProcessor.sol. It's defined here in order to be able to receive ETH like a real | ||
// rollup processor would. | ||
function receiveEthFromBridge(uint256 _interactionNonce) external payable {} | ||
|
||
function setUp() public { | ||
// In unit tests we set address of rollupProcessor to the address of this test contract | ||
rollupProcessor = address(this); | ||
|
||
// Deploy a new example bridge | ||
bridge = new ExampleBridge(rollupProcessor); | ||
|
||
// Set ETH balance of bridge and BENEFICIARY to 0 for clarity (somebody sent ETH to that address on mainnet) | ||
vm.deal(address(bridge), 0); | ||
vm.deal(BENEFICIARY, 0); | ||
|
||
// Use the label cheatcode to mark the address with "Example Bridge" in the traces | ||
vm.label(address(bridge), "Example Bridge"); | ||
|
||
// Subsidize the bridge when used with Dai and register a beneficiary | ||
AztecTypes.AztecAsset memory daiAsset = ROLLUP_ENCODER.getRealAztecAsset(DAI); | ||
uint256 criteria = bridge.computeCriteria(daiAsset, emptyAsset, daiAsset, emptyAsset, 0); | ||
uint32 gasPerMinute = 200; | ||
SUBSIDY.subsidize{value: 1 ether}(address(bridge), criteria, gasPerMinute); | ||
|
||
SUBSIDY.registerBeneficiary(BENEFICIARY); | ||
} | ||
|
||
function testInvalidCaller(address _callerAddress) public { | ||
vm.assume(_callerAddress != rollupProcessor); | ||
// Use HEVM cheatcode to call from a different address than is address(this) | ||
vm.prank(_callerAddress); | ||
vm.expectRevert(ErrorLib.InvalidCaller.selector); | ||
bridge.convert(emptyAsset, emptyAsset, emptyAsset, emptyAsset, 0, 0, 0, address(0)); | ||
} | ||
|
||
function testInvalidInputAssetType() public { | ||
vm.expectRevert(ErrorLib.InvalidInputA.selector); | ||
bridge.convert(emptyAsset, emptyAsset, emptyAsset, emptyAsset, 0, 0, 0, address(0)); | ||
} | ||
|
||
function testInvalidOutputAssetType() public { | ||
AztecTypes.AztecAsset memory inputAssetA = | ||
AztecTypes.AztecAsset({id: 1, erc20Address: DAI, assetType: AztecTypes.AztecAssetType.ERC20}); | ||
vm.expectRevert(ErrorLib.InvalidOutputA.selector); | ||
bridge.convert(inputAssetA, emptyAsset, emptyAsset, emptyAsset, 0, 0, 0, address(0)); | ||
} | ||
|
||
function testExampleBridgeUnitTestFixed() public { | ||
testExampleBridgeUnitTest(10 ether); | ||
} | ||
|
||
// @notice The purpose of this test is to directly test convert functionality of the bridge. | ||
// @dev In order to avoid overflows we set _depositAmount to be uint96 instead of uint256. | ||
function testExampleBridgeUnitTest(uint96 _depositAmount) public { | ||
vm.warp(block.timestamp + 1 days); | ||
|
||
// Define input and output assets | ||
AztecTypes.AztecAsset memory inputAssetA = | ||
AztecTypes.AztecAsset({id: 1, erc20Address: DAI, assetType: AztecTypes.AztecAssetType.ERC20}); | ||
|
||
AztecTypes.AztecAsset memory outputAssetA = inputAssetA; | ||
|
||
// Rollup processor transfers ERC20 tokens to the bridge before calling convert. Since we are calling | ||
// bridge.convert(...) function directly we have to transfer the funds in the test on our own. In this case | ||
// we'll solve it by directly minting the _depositAmount of Dai to the bridge. | ||
deal(DAI, address(bridge), _depositAmount); | ||
|
||
// Store dai balance before interaction to be able to verify the balance after interaction is correct | ||
uint256 daiBalanceBefore = IERC20(DAI).balanceOf(rollupProcessor); | ||
|
||
(uint256 outputValueA, uint256 outputValueB, bool isAsync) = bridge.convert( | ||
inputAssetA, // _inputAssetA - definition of an input asset | ||
emptyAsset, // _inputAssetB - not used so can be left empty | ||
outputAssetA, // _outputAssetA - in this example equal to input asset | ||
emptyAsset, // _outputAssetB - not used so can be left empty | ||
_depositAmount, // _totalInputValue - an amount of input asset A sent to the bridge | ||
0, // _interactionNonce | ||
0, // _auxData - not used in the example bridge | ||
BENEFICIARY // _rollupBeneficiary - address, the subsidy will be sent to | ||
); | ||
|
||
// Now we transfer the funds back from the bridge to the rollup processor | ||
// In this case input asset equals output asset so I only work with the input asset definition | ||
// Basically in all the real world use-cases output assets would differ from input assets | ||
IERC20(inputAssetA.erc20Address).transferFrom(address(bridge), rollupProcessor, outputValueA); | ||
|
||
assertEq(outputValueA, _depositAmount, "Output value A doesn't equal deposit amount"); | ||
assertEq(outputValueB, 0, "Output value B is not 0"); | ||
assertTrue(!isAsync, "Bridge is incorrectly in an async mode"); | ||
|
||
uint256 daiBalanceAfter = IERC20(DAI).balanceOf(rollupProcessor); | ||
|
||
assertEq(daiBalanceAfter - daiBalanceBefore, _depositAmount, "Balances must match"); | ||
|
||
SUBSIDY.withdraw(BENEFICIARY); | ||
assertGt(BENEFICIARY.balance, 0, "Subsidy was not claimed"); | ||
} | ||
} |