Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ jobs:

name: Foundry project
runs-on: ubuntu-latest
env:
# should support archive requests
FORK_URL: 'https://eth.llamarpc.com'
FORK_BLOCK_NUMBER: 20691292
steps:
- uses: actions/checkout@v4
with:
Expand Down
2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ deny_warnings = true
fs_permissions = [
{ access = "read", path = "./balancer"},
{ access = "read", path = "./networks.json"},
{ access = "read", path = "./out"}
{ access = "read", path = "./out"},
]

[fmt]
Expand Down
17 changes: 17 additions & 0 deletions test/e2e/ERC20Mintable.sol
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);
}
}
221 changes: 221 additions & 0 deletions test/e2e/Helper.sol
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);

// 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);
}
}
62 changes: 62 additions & 0 deletions test/e2e/WETH9.sol
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;
}
}
Loading
Loading