Skip to content

Commit f176400

Browse files
test: helper base for e2e tests (#215)
## Description Base test contract for forked tests in e2e tests ## Test Plan CI ## Related Issues
1 parent 1ff7aef commit f176400

File tree

7 files changed

+413
-1
lines changed

7 files changed

+413
-1
lines changed

.github/workflows/test.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ jobs:
3131

3232
name: Foundry project
3333
runs-on: ubuntu-latest
34+
env:
35+
# should support archive requests
36+
FORK_URL: 'https://eth.llamarpc.com'
37+
FORK_BLOCK_NUMBER: 20691292
3438
steps:
3539
- uses: actions/checkout@v4
3640
with:

foundry.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ deny_warnings = true
1414
fs_permissions = [
1515
{ access = "read", path = "./balancer"},
1616
{ access = "read", path = "./networks.json"},
17-
{ access = "read", path = "./out"}
17+
{ access = "read", path = "./out"},
1818
]
1919

2020
[fmt]

test/e2e/ERC20Mintable.sol

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// SPDX-License-Identifier: LGPL-3.0-or-later
2+
// solhint-disable-next-line compiler-version
3+
pragma solidity ^0.7;
4+
5+
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
6+
7+
contract ERC20Mintable is ERC20 {
8+
constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_) {}
9+
10+
function mint(address account, uint256 amount) external {
11+
_mint(account, amount);
12+
}
13+
14+
function burn(uint256 amount) external {
15+
_burn(msg.sender, amount);
16+
}
17+
}

test/e2e/Helper.sol

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
// SPDX-License-Identifier: LGPL-3.0-or-later
2+
pragma solidity ^0.8;
3+
4+
import {Test, Vm, stdJson} from "forge-std/Test.sol";
5+
import {IERC20} from "src/contracts/interfaces/IERC20.sol";
6+
7+
import {GPv2AllowListAuthentication} from "src/contracts/GPv2AllowListAuthentication.sol";
8+
import {
9+
GPv2Authentication,
10+
GPv2Interaction,
11+
GPv2Settlement,
12+
GPv2Trade,
13+
IERC20,
14+
IVault
15+
} from "src/contracts/GPv2Settlement.sol";
16+
17+
import {WETH9} from "./WETH9.sol";
18+
import {SettlementEncoder} from "test/libraries/encoders/SettlementEncoder.sol";
19+
import {SwapEncoder} from "test/libraries/encoders/SwapEncoder.sol";
20+
21+
address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
22+
address constant BALANCER_VAULT = 0xBA12222222228d8Ba445958a75a0704d566BF2C8;
23+
24+
interface IAuthorizer {
25+
function grantRole(bytes32, address) external;
26+
function canPerform(bytes32 actionId, address account, address where) external view returns (bool);
27+
}
28+
29+
interface IERC20Mintable is IERC20 {
30+
function mint(address, uint256) external;
31+
function burn(uint256) external;
32+
}
33+
34+
interface IBalancerVault is IVault {
35+
function getAuthorizer() external view returns (address);
36+
}
37+
38+
// solhint-disable func-name-mixedcase
39+
// solhint-disable max-states-count
40+
abstract contract Helper is Test {
41+
using stdJson for string;
42+
using SettlementEncoder for SettlementEncoder.State;
43+
44+
address internal deployer;
45+
address internal owner;
46+
GPv2Settlement internal settlement;
47+
bytes32 internal domainSeparator;
48+
GPv2Authentication internal authenticator;
49+
IVault internal vault;
50+
GPv2AllowListAuthentication internal allowList;
51+
GPv2AllowListAuthentication internal allowListImpl;
52+
address vaultRelayer;
53+
address balancerVaultAuthorizer;
54+
55+
SettlementEncoder.State internal encoder;
56+
SwapEncoder.State internal swapEncoder;
57+
58+
address internal solver;
59+
Vm.Wallet internal trader;
60+
61+
bool immutable isForked;
62+
uint256 forkId;
63+
64+
WETH9 weth;
65+
66+
bytes32 constant SALT = "Mattresses in Berlin!";
67+
68+
constructor(bool _isForked) {
69+
isForked = _isForked;
70+
}
71+
72+
function setUp() public virtual {
73+
if (isForked) {
74+
uint256 blockNumber = vm.envUint("FORK_BLOCK_NUMBER");
75+
string memory forkUrl;
76+
77+
try vm.envString("FORK_URL") returns (string memory url) {
78+
forkUrl = url;
79+
} catch {
80+
forkUrl = "https://eth.llamarpc.com";
81+
}
82+
83+
forkId = vm.createSelectFork(forkUrl, blockNumber);
84+
weth = WETH9(payable(WETH));
85+
} else {
86+
weth = new WETH9();
87+
}
88+
89+
// Configure addresses
90+
deployer = makeAddr("E2E.Helper: deployer");
91+
owner = makeAddr("E2E.Helper: owner");
92+
solver = makeAddr("E2E.Helper: solver");
93+
vm.startPrank(deployer);
94+
95+
// Deploy the allowlist manager
96+
allowListImpl = new GPv2AllowListAuthentication{salt: SALT}();
97+
allowList = GPv2AllowListAuthentication(
98+
deployProxy(address(allowListImpl), owner, abi.encodeCall(allowListImpl.initializeManager, (owner)), SALT)
99+
);
100+
authenticator = allowList;
101+
102+
(balancerVaultAuthorizer, vault) = _deployBalancerVault();
103+
104+
// Deploy the settlement contract
105+
settlement = new GPv2Settlement{salt: SALT}(authenticator, vault);
106+
vaultRelayer = address(settlement.vaultRelayer());
107+
108+
// Reset the prank
109+
vm.stopPrank();
110+
111+
_grantBalancerRolesToRelayer(balancerVaultAuthorizer, address(vault), vaultRelayer);
112+
113+
// By default, allow `solver` to settle
114+
vm.prank(owner);
115+
allowList.addSolver(solver);
116+
117+
// Configure default encoders
118+
encoder = SettlementEncoder.makeSettlementEncoder();
119+
swapEncoder = SwapEncoder.makeSwapEncoder();
120+
121+
// Set the domain separator
122+
domainSeparator = settlement.domainSeparator();
123+
124+
// Create wallets
125+
trader = vm.createWallet("E2E.Helper: trader");
126+
}
127+
128+
function settle(SettlementEncoder.EncodedSettlement memory _settlement) internal {
129+
settlement.settle(_settlement.tokens, _settlement.clearingPrices, _settlement.trades, _settlement.interactions);
130+
}
131+
132+
function swap(SwapEncoder.EncodedSwap memory _swap) internal {
133+
settlement.swap(_swap.swaps, _swap.tokens, _swap.trade);
134+
}
135+
136+
function emptySettlement() internal pure returns (SettlementEncoder.EncodedSettlement memory) {
137+
return SettlementEncoder.EncodedSettlement({
138+
tokens: new IERC20[](0),
139+
clearingPrices: new uint256[](0),
140+
trades: new GPv2Trade.Data[](0),
141+
interactions: [new GPv2Interaction.Data[](0), new GPv2Interaction.Data[](0), new GPv2Interaction.Data[](0)]
142+
});
143+
}
144+
145+
function _deployBalancerVault() internal returns (address, IBalancerVault) {
146+
if (isForked) {
147+
IBalancerVault balancerVault = IBalancerVault(BALANCER_VAULT);
148+
address authorizer = balancerVault.getAuthorizer();
149+
return (authorizer, balancerVault);
150+
} else {
151+
bytes memory authorizerInitCode = abi.encodePacked(_getBalancerBytecode("Authorizer"), abi.encode(owner));
152+
address authorizer = _create(authorizerInitCode, 0);
153+
154+
bytes memory vaultInitCode =
155+
abi.encodePacked(_getBalancerBytecode("Vault"), abi.encode(authorizer, address(weth), 0, 0));
156+
address deployedVault = _create(vaultInitCode, 0);
157+
158+
return (authorizer, IBalancerVault(deployedVault));
159+
}
160+
}
161+
162+
function _grantBalancerRolesToRelayer(address authorizer, address deployedVault, address relayer) internal {
163+
_grantBalancerActionRole(
164+
authorizer, deployedVault, relayer, "manageUserBalance((uint8,address,uint256,address,address)[])"
165+
);
166+
_grantBalancerActionRole(
167+
authorizer,
168+
deployedVault,
169+
relayer,
170+
"batchSwap(uint8,(bytes32,uint256,uint256,uint256,bytes)[],address[],(address,bool,address,bool),int256[],uint256)"
171+
);
172+
}
173+
174+
function _grantBalancerActionRole(address authorizer, address balVault, address to, string memory action)
175+
internal
176+
{
177+
bytes32 actionId = _getActionId(action, balVault);
178+
vm.mockCall(
179+
address(authorizer), abi.encodeCall(IAuthorizer.canPerform, (actionId, to, balVault)), abi.encode(true)
180+
);
181+
}
182+
183+
function _getActionId(string memory fnDef, address vaultAddr) internal pure returns (bytes32) {
184+
bytes32 hash = keccak256(bytes(fnDef));
185+
bytes4 selector = bytes4(hash);
186+
return keccak256(abi.encodePacked(uint256(uint160(vaultAddr)), selector));
187+
}
188+
189+
function _getBalancerBytecode(string memory artifactName) internal view returns (bytes memory) {
190+
string memory data = vm.readFile(string(abi.encodePacked("balancer/", artifactName, ".json")));
191+
return vm.parseJsonBytes(data, ".bytecode");
192+
}
193+
194+
function _create(bytes memory initCode, uint256 value) internal returns (address deployed) {
195+
assembly ("memory-safe") {
196+
deployed := create(value, add(initCode, 0x20), mload(initCode))
197+
}
198+
require(deployed != address(0), "deployment failed");
199+
}
200+
201+
function _create2(bytes memory initCode, uint256 value, bytes32 salt) internal returns (address deployed) {
202+
assembly ("memory-safe") {
203+
deployed := create2(value, add(initCode, 0x20), mload(initCode), salt)
204+
}
205+
require(deployed != address(0), "deployment failed");
206+
}
207+
208+
function deployMintableErc20(string memory name, string memory symbol) internal returns (IERC20Mintable token) {
209+
// need to use like this because OZ requires ^0.7 and tests are on ^0.8
210+
bytes memory initCode = abi.encodePacked(vm.getCode("ERC20Mintable"), abi.encode(name, symbol));
211+
token = IERC20Mintable(_create(initCode, 0));
212+
}
213+
214+
function deployProxy(address implAddress, address ownerAddress, bytes memory data, bytes32 salt)
215+
internal
216+
returns (address proxy)
217+
{
218+
proxy =
219+
_create2(abi.encodePacked(vm.getCode("EIP173Proxy"), abi.encode(implAddress, ownerAddress, data)), 0, salt);
220+
}
221+
}

test/e2e/WETH9.sol

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// SPDX-License-Identifier: LGPL-3.0-or-later
2+
pragma solidity ^0.8;
3+
4+
contract WETH9 {
5+
string public name = "Wrapped Ether";
6+
string public symbol = "WETH";
7+
uint8 public decimals = 18;
8+
9+
event Approval(address indexed src, address indexed guy, uint256 wad);
10+
event Transfer(address indexed src, address indexed dst, uint256 wad);
11+
event Deposit(address indexed dst, uint256 wad);
12+
event Withdrawal(address indexed src, uint256 wad);
13+
14+
mapping(address => uint256) public balanceOf;
15+
mapping(address => mapping(address => uint256)) public allowance;
16+
17+
receive() external payable {
18+
deposit();
19+
}
20+
21+
function deposit() public payable {
22+
balanceOf[msg.sender] += msg.value;
23+
emit Deposit(msg.sender, msg.value);
24+
}
25+
26+
function withdraw(uint256 wad) public {
27+
require(balanceOf[msg.sender] >= wad);
28+
balanceOf[msg.sender] -= wad;
29+
payable(msg.sender).transfer(wad);
30+
emit Withdrawal(msg.sender, wad);
31+
}
32+
33+
function totalSupply() public view returns (uint256) {
34+
return address(this).balance;
35+
}
36+
37+
function approve(address guy, uint256 wad) public returns (bool) {
38+
allowance[msg.sender][guy] = wad;
39+
emit Approval(msg.sender, guy, wad);
40+
return true;
41+
}
42+
43+
function transfer(address dst, uint256 wad) public returns (bool) {
44+
return transferFrom(msg.sender, dst, wad);
45+
}
46+
47+
function transferFrom(address src, address dst, uint256 wad) public returns (bool) {
48+
require(balanceOf[src] >= wad);
49+
50+
if (src != msg.sender && allowance[src][msg.sender] != type(uint256).max) {
51+
require(allowance[src][msg.sender] >= wad);
52+
allowance[src][msg.sender] -= wad;
53+
}
54+
55+
balanceOf[src] -= wad;
56+
balanceOf[dst] += wad;
57+
58+
emit Transfer(src, dst, wad);
59+
60+
return true;
61+
}
62+
}

0 commit comments

Comments
 (0)