-
Notifications
You must be signed in to change notification settings - Fork 48
test: helper base for e2e tests #215
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
Merged
mfw78
merged 18 commits into
cowprotocol:e2e-migration
from
meetmangukiya:e2e-ForkedTest
Oct 7, 2024
+413
−1
Merged
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
9e7e5ca
test: ForkedTest base test contract
meetmangukiya a55ff8d
chore: refactor to e2e helper with both forked and non forked setup
meetmangukiya 504c197
chore: add ERC20Mintable
meetmangukiya 4a5051c
chore: soft link ERC20Mintable into test/src/
meetmangukiya 13b8e3d
fix: remove the mockCallRevert
meetmangukiya a6cb7e4
fix: always deploy and use local deployment of balancer vaults
meetmangukiya c0d43a4
chore: deploy the allowlist behind a eip173 proxy
meetmangukiya 1c4fab7
refactor: modular balancer action role grant fn
meetmangukiya d3511df
chore: fmt
meetmangukiya 9b4b1e7
chore: add burn to ERC20Mintable
meetmangukiya 6521613
chore: read permissions for contract artifacts
meetmangukiya 532dc88
chore: remove harness from e2e helper
meetmangukiya 49e9dab
chore: deterministic deployment in tests
meetmangukiya 77e8a26
chore: fix solhint
meetmangukiya 5db01af
chore: address review comments
meetmangukiya 6f47c4b
chore: remove node_modules artifact uses
meetmangukiya 7ab91ff
chore: fix ci
meetmangukiya d7a9bfb
refactor: use production balancer deployment in forked tests
meetmangukiya File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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
This file contains hidden or 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
This file contains hidden or 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,17 @@ | ||
| // SPDX-License-Identifier: LGPL-3.0-or-later | ||
| // solhint-disable-next-line compiler-version | ||
| pragma solidity ^0.7; | ||
|
|
||
| import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; | ||
|
|
||
| contract ERC20Mintable is ERC20 { | ||
| constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_) {} | ||
|
|
||
| function mint(address account, uint256 amount) external { | ||
| _mint(account, amount); | ||
| } | ||
|
|
||
| function burn(uint256 amount) external { | ||
| _burn(msg.sender, amount); | ||
| } | ||
| } |
This file contains hidden or 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,221 @@ | ||
| // SPDX-License-Identifier: LGPL-3.0-or-later | ||
| pragma solidity ^0.8; | ||
|
|
||
| import {Test, Vm, stdJson} from "forge-std/Test.sol"; | ||
| import {IERC20} from "src/contracts/interfaces/IERC20.sol"; | ||
|
|
||
| import {GPv2AllowListAuthentication} from "src/contracts/GPv2AllowListAuthentication.sol"; | ||
| import { | ||
| GPv2Authentication, | ||
| GPv2Interaction, | ||
| GPv2Settlement, | ||
| GPv2Trade, | ||
| IERC20, | ||
| IVault | ||
| } from "src/contracts/GPv2Settlement.sol"; | ||
|
|
||
| import {WETH9} from "./WETH9.sol"; | ||
| import {SettlementEncoder} from "test/libraries/encoders/SettlementEncoder.sol"; | ||
| import {SwapEncoder} from "test/libraries/encoders/SwapEncoder.sol"; | ||
|
|
||
| address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; | ||
| address constant BALANCER_VAULT = 0xBA12222222228d8Ba445958a75a0704d566BF2C8; | ||
|
|
||
| interface IAuthorizer { | ||
| function grantRole(bytes32, address) external; | ||
| function canPerform(bytes32 actionId, address account, address where) external view returns (bool); | ||
| } | ||
|
|
||
| interface IERC20Mintable is IERC20 { | ||
| function mint(address, uint256) external; | ||
| function burn(uint256) external; | ||
| } | ||
|
|
||
| interface IBalancerVault is IVault { | ||
| function getAuthorizer() external view returns (address); | ||
| } | ||
|
|
||
| // solhint-disable func-name-mixedcase | ||
| // solhint-disable max-states-count | ||
| abstract contract Helper is Test { | ||
| using stdJson for string; | ||
| using SettlementEncoder for SettlementEncoder.State; | ||
|
|
||
| address internal deployer; | ||
| address internal owner; | ||
| GPv2Settlement internal settlement; | ||
| bytes32 internal domainSeparator; | ||
| GPv2Authentication internal authenticator; | ||
| IVault internal vault; | ||
| GPv2AllowListAuthentication internal allowList; | ||
| GPv2AllowListAuthentication internal allowListImpl; | ||
| address vaultRelayer; | ||
| address balancerVaultAuthorizer; | ||
|
|
||
| SettlementEncoder.State internal encoder; | ||
| SwapEncoder.State internal swapEncoder; | ||
|
|
||
| address internal solver; | ||
| Vm.Wallet internal trader; | ||
|
|
||
| bool immutable isForked; | ||
| uint256 forkId; | ||
|
|
||
| WETH9 weth; | ||
|
|
||
| bytes32 constant SALT = "Mattresses in Berlin!"; | ||
|
|
||
| constructor(bool _isForked) { | ||
| isForked = _isForked; | ||
| } | ||
|
|
||
| function setUp() public virtual { | ||
| if (isForked) { | ||
| uint256 blockNumber = vm.envUint("FORK_BLOCK_NUMBER"); | ||
| string memory forkUrl; | ||
|
|
||
| try vm.envString("FORK_URL") returns (string memory url) { | ||
| forkUrl = url; | ||
| } catch { | ||
| forkUrl = "https://eth.llamarpc.com"; | ||
| } | ||
|
|
||
| forkId = vm.createSelectFork(forkUrl, blockNumber); | ||
| weth = WETH9(payable(WETH)); | ||
| } else { | ||
| weth = new WETH9(); | ||
| } | ||
|
|
||
| // Configure addresses | ||
| deployer = makeAddr("E2E.Helper: deployer"); | ||
| owner = makeAddr("E2E.Helper: owner"); | ||
| solver = makeAddr("E2E.Helper: solver"); | ||
| vm.startPrank(deployer); | ||
|
|
||
| // Deploy the allowlist manager | ||
| allowListImpl = new GPv2AllowListAuthentication{salt: SALT}(); | ||
| allowList = GPv2AllowListAuthentication( | ||
| deployProxy(address(allowListImpl), owner, abi.encodeCall(allowListImpl.initializeManager, (owner)), SALT) | ||
| ); | ||
| authenticator = allowList; | ||
|
|
||
| (balancerVaultAuthorizer, vault) = _deployBalancerVault(); | ||
|
|
||
| // Deploy the settlement contract | ||
| settlement = new GPv2Settlement{salt: SALT}(authenticator, vault); | ||
| vaultRelayer = address(settlement.vaultRelayer()); | ||
|
|
||
| // Reset the prank | ||
| vm.stopPrank(); | ||
|
|
||
| _grantBalancerRolesToRelayer(balancerVaultAuthorizer, address(vault), vaultRelayer); | ||
|
|
||
meetmangukiya marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // By default, allow `solver` to settle | ||
| vm.prank(owner); | ||
| allowList.addSolver(solver); | ||
|
|
||
| // Configure default encoders | ||
| encoder = SettlementEncoder.makeSettlementEncoder(); | ||
| swapEncoder = SwapEncoder.makeSwapEncoder(); | ||
|
|
||
| // Set the domain separator | ||
| domainSeparator = settlement.domainSeparator(); | ||
|
|
||
| // Create wallets | ||
| trader = vm.createWallet("E2E.Helper: trader"); | ||
| } | ||
|
|
||
| function settle(SettlementEncoder.EncodedSettlement memory _settlement) internal { | ||
| settlement.settle(_settlement.tokens, _settlement.clearingPrices, _settlement.trades, _settlement.interactions); | ||
| } | ||
|
|
||
| function swap(SwapEncoder.EncodedSwap memory _swap) internal { | ||
| settlement.swap(_swap.swaps, _swap.tokens, _swap.trade); | ||
| } | ||
|
|
||
| function emptySettlement() internal pure returns (SettlementEncoder.EncodedSettlement memory) { | ||
| return SettlementEncoder.EncodedSettlement({ | ||
| tokens: new IERC20[](0), | ||
| clearingPrices: new uint256[](0), | ||
| trades: new GPv2Trade.Data[](0), | ||
| interactions: [new GPv2Interaction.Data[](0), new GPv2Interaction.Data[](0), new GPv2Interaction.Data[](0)] | ||
| }); | ||
| } | ||
|
|
||
| function _deployBalancerVault() internal returns (address, IBalancerVault) { | ||
| if (isForked) { | ||
| IBalancerVault balancerVault = IBalancerVault(BALANCER_VAULT); | ||
| address authorizer = balancerVault.getAuthorizer(); | ||
| return (authorizer, balancerVault); | ||
| } else { | ||
| bytes memory authorizerInitCode = abi.encodePacked(_getBalancerBytecode("Authorizer"), abi.encode(owner)); | ||
| address authorizer = _create(authorizerInitCode, 0); | ||
|
|
||
| bytes memory vaultInitCode = | ||
| abi.encodePacked(_getBalancerBytecode("Vault"), abi.encode(authorizer, address(weth), 0, 0)); | ||
| address deployedVault = _create(vaultInitCode, 0); | ||
|
|
||
| return (authorizer, IBalancerVault(deployedVault)); | ||
| } | ||
| } | ||
|
|
||
| function _grantBalancerRolesToRelayer(address authorizer, address deployedVault, address relayer) internal { | ||
| _grantBalancerActionRole( | ||
| authorizer, deployedVault, relayer, "manageUserBalance((uint8,address,uint256,address,address)[])" | ||
| ); | ||
| _grantBalancerActionRole( | ||
| authorizer, | ||
| deployedVault, | ||
| relayer, | ||
| "batchSwap(uint8,(bytes32,uint256,uint256,uint256,bytes)[],address[],(address,bool,address,bool),int256[],uint256)" | ||
| ); | ||
| } | ||
|
|
||
| function _grantBalancerActionRole(address authorizer, address balVault, address to, string memory action) | ||
| internal | ||
| { | ||
| bytes32 actionId = _getActionId(action, balVault); | ||
| vm.mockCall( | ||
| address(authorizer), abi.encodeCall(IAuthorizer.canPerform, (actionId, to, balVault)), abi.encode(true) | ||
| ); | ||
| } | ||
|
|
||
| function _getActionId(string memory fnDef, address vaultAddr) internal pure returns (bytes32) { | ||
| bytes32 hash = keccak256(bytes(fnDef)); | ||
| bytes4 selector = bytes4(hash); | ||
| return keccak256(abi.encodePacked(uint256(uint160(vaultAddr)), selector)); | ||
| } | ||
|
|
||
| function _getBalancerBytecode(string memory artifactName) internal view returns (bytes memory) { | ||
| string memory data = vm.readFile(string(abi.encodePacked("balancer/", artifactName, ".json"))); | ||
| return vm.parseJsonBytes(data, ".bytecode"); | ||
| } | ||
|
|
||
| function _create(bytes memory initCode, uint256 value) internal returns (address deployed) { | ||
| assembly ("memory-safe") { | ||
| deployed := create(value, add(initCode, 0x20), mload(initCode)) | ||
| } | ||
| require(deployed != address(0), "deployment failed"); | ||
| } | ||
|
|
||
| function _create2(bytes memory initCode, uint256 value, bytes32 salt) internal returns (address deployed) { | ||
| assembly ("memory-safe") { | ||
| deployed := create2(value, add(initCode, 0x20), mload(initCode), salt) | ||
| } | ||
| require(deployed != address(0), "deployment failed"); | ||
| } | ||
|
|
||
| function deployMintableErc20(string memory name, string memory symbol) internal returns (IERC20Mintable token) { | ||
| // need to use like this because OZ requires ^0.7 and tests are on ^0.8 | ||
| bytes memory initCode = abi.encodePacked(vm.getCode("ERC20Mintable"), abi.encode(name, symbol)); | ||
| token = IERC20Mintable(_create(initCode, 0)); | ||
| } | ||
|
|
||
| function deployProxy(address implAddress, address ownerAddress, bytes memory data, bytes32 salt) | ||
| internal | ||
| returns (address proxy) | ||
| { | ||
| proxy = | ||
| _create2(abi.encodePacked(vm.getCode("EIP173Proxy"), abi.encode(implAddress, ownerAddress, data)), 0, salt); | ||
| } | ||
| } | ||
This file contains hidden or 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,62 @@ | ||
| // SPDX-License-Identifier: LGPL-3.0-or-later | ||
| pragma solidity ^0.8; | ||
|
|
||
| contract WETH9 { | ||
| string public name = "Wrapped Ether"; | ||
| string public symbol = "WETH"; | ||
| uint8 public decimals = 18; | ||
|
|
||
| event Approval(address indexed src, address indexed guy, uint256 wad); | ||
| event Transfer(address indexed src, address indexed dst, uint256 wad); | ||
| event Deposit(address indexed dst, uint256 wad); | ||
| event Withdrawal(address indexed src, uint256 wad); | ||
|
|
||
| mapping(address => uint256) public balanceOf; | ||
| mapping(address => mapping(address => uint256)) public allowance; | ||
|
|
||
| receive() external payable { | ||
| deposit(); | ||
| } | ||
|
|
||
| function deposit() public payable { | ||
| balanceOf[msg.sender] += msg.value; | ||
| emit Deposit(msg.sender, msg.value); | ||
| } | ||
|
|
||
| function withdraw(uint256 wad) public { | ||
| require(balanceOf[msg.sender] >= wad); | ||
| balanceOf[msg.sender] -= wad; | ||
| payable(msg.sender).transfer(wad); | ||
| emit Withdrawal(msg.sender, wad); | ||
| } | ||
|
|
||
| function totalSupply() public view returns (uint256) { | ||
| return address(this).balance; | ||
| } | ||
|
|
||
| function approve(address guy, uint256 wad) public returns (bool) { | ||
| allowance[msg.sender][guy] = wad; | ||
| emit Approval(msg.sender, guy, wad); | ||
| return true; | ||
| } | ||
|
|
||
| function transfer(address dst, uint256 wad) public returns (bool) { | ||
| return transferFrom(msg.sender, dst, wad); | ||
| } | ||
|
|
||
| function transferFrom(address src, address dst, uint256 wad) public returns (bool) { | ||
| require(balanceOf[src] >= wad); | ||
|
|
||
| if (src != msg.sender && allowance[src][msg.sender] != type(uint256).max) { | ||
| require(allowance[src][msg.sender] >= wad); | ||
| allowance[src][msg.sender] -= wad; | ||
| } | ||
|
|
||
| balanceOf[src] -= wad; | ||
| balanceOf[dst] += wad; | ||
|
|
||
| emit Transfer(src, dst, wad); | ||
|
|
||
| return true; | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.