Skip to content
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

feat: community-proposal #5

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
93 changes: 93 additions & 0 deletions community-transactions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
{
"version": "1.0",
"chainId": "1",
"createdAt": 1703230404439,
"meta": {
"name": "Transactions Batch",
"description": "",
"txBuilderVersion": "1.16.3",
"createdFromSafeAddress": "0xf2964cCcB7CDA9e808aaBe8DB0DDDAF7890dd378",
"createdFromOwnerAddress": "",
"checksum": "0xbc303aae88a7c32de7c047b809fe56f676640630255cce46ad7d783348f452e7"
},
"transactions": [
{
"to": "0xFE67A4450907459c3e1FFf623aA927dD4e28c67a",
"value": "0",
"data": "0x095ea7b300000000000000000000000022f424bca11fe154c403c277b5f8dab54a4ba29b00000000000000000000000000000000000000000000fe1c215e8f838e000000",
"contractMethod": {
"inputs": [
{ "internalType": "address", "name": "spender", "type": "address" },
{ "internalType": "uint256", "name": "amount", "type": "uint256" }
],
"name": "approve",
"payable": false
},
"contractInputsValues": {
"spender": "0x22f424Bca11FE154c403c277b5F8dAb54a4bA29b",
"amount": "1200000000000000000000000"
}
},
{
"to": "0x22f424Bca11FE154c403c277b5F8dAb54a4bA29b",
"value": "0",
"data": "0xb6b55f2500000000000000000000000000000000000000000000fe1c215e8f838e000000",
"contractMethod": {
"inputs": [
{ "internalType": "uint256", "name": "_amount", "type": "uint256" }
],
"name": "deposit",
"payable": false
},
"contractInputsValues": { "_amount": "1200000000000000000000000" }
},
{
"to": "0x58b9cB810A68a7f3e1E4f8Cb45D1B9B3c79705E8",
"value": "0",
"data": "0x095ea7b30000000000000000000000008898b472c54c31894e3b9bb83cea802a5d0e63c600000000000000000000000000000000000000000000fe1c215e8f838e000000",
"contractMethod": {
"inputs": [
{ "internalType": "address", "name": "spender", "type": "address" },
{ "internalType": "uint256", "name": "amount", "type": "uint256" }
],
"name": "approve",
"payable": false
},
"contractInputsValues": {
"spender": "0x8898B472C54c31894e3B9bb83cEA802a5d0e63C6",
"amount": "1200000000000000000000000"
}
},
{
"to": "0x8898B472C54c31894e3B9bb83cEA802a5d0e63C6",
"value": "0",
"data": "0x8aac16ba00000000000000000000000000000000000000000000000000000000706f6c79000000000000000000000000b369f4d4b8b5e33516418ddd877194530812aa7100000000000000000000000058b9cb810a68a7f3e1e4f8cb45d1b9b3c79705e8000000000000000000000000b369f4d4b8b5e33516418ddd877194530812aa7100000000000000000000000000000000000000000000fe1c215e8f838e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000",
"contractMethod": {
"inputs": [
{
"internalType": "uint32",
"name": "_destination",
"type": "uint32"
},
{ "internalType": "address", "name": "_to", "type": "address" },
{ "internalType": "address", "name": "_asset", "type": "address" },
{ "internalType": "address", "name": "_delegate", "type": "address" },
{ "internalType": "uint256", "name": "_amount", "type": "uint256" },
{ "internalType": "uint256", "name": "_slippage", "type": "uint256" },
{ "internalType": "bytes", "name": "_callData", "type": "bytes" }
],
"name": "xcall",
"payable": true
},
"contractInputsValues": {
"_destination": "1886350457",
"_to": "0xb369f4d4B8b5e33516418dDd877194530812AA71",
"_asset": "0x58b9cB810A68a7f3e1E4f8Cb45D1B9B3c79705E8",
"_delegate": "0xb369f4d4B8b5e33516418dDd877194530812AA71",
"_amount": "1200000000000000000000000",
"_slippage": "0",
"_callData": "0x"
}
}
]
}
203 changes: 203 additions & 0 deletions test/CommunityProposal.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {Strings} from "@openzeppelin/utils/Strings.sol";
import {Ownable} from "@openzeppelin/access/Ownable.sol";
import {IERC20} from "@openzeppelin/token/ERC20/IERC20.sol";

import {MultiSendCallOnly} from "safe-contracts/libraries/MultiSendCallOnly.sol";

import {IXReceiver} from "@connext/interfaces/core/IXReceiver.sol";

import {IXERC20} from "./interfaces/IXERC20.sol";

import {ForgeHelper} from "./utils/ForgeHelper.sol";
import {ForkHelper} from "./utils/ForkHelper.sol";
import {AddressLookup} from "./utils/AddressLookup.sol";
import {ChainLookup} from "./utils/ChainLookup.sol";

import "forge-std/StdJson.sol";
import "forge-std/console.sol";

// Addresses ----

// 0xFE67A4450907459c3e1FFf623aA927dD4e28c67a - mainnet NEXT token (token1 on mainnet vault)

// 0x58b9cb810a68a7f3e1e4f8cb45d1b9b3c79705e8 - Polygon NEXT token (token0 on arb vault)

// --------

contract CommunityProposal is ForgeHelper {
enum Operation {
Call,
DelegateCall
}

struct Transaction {
address to;
uint256 value;
bytes data;
Operation operation;
}

// ================== Libraries ==================
using stdJson for string;
using Strings for string;
using Strings for uint256;

// ================== Events ==================

// ================== Structs ==================

// ================== Storage ==================

// Fork management utilities
ForkHelper public FORK_HELPER;

// Transactions path
string public TRANSACTIONS_PATH = "/community-transactions.json";

// Number of transactions to execute in multisend data:
// 1. mainnet approval of NEXT to lockbox
// 2. mainnet deposit on lockbox
// 3. mainnet approval of xNEXT to connext
// 4. xcall xNEXT into connext

uint256 public NUMBER_TRANSACTIONS = 4;

// Amount to bridge into POL multisig
uint256 public LIQUIDITY_AMOUNT_POLYGON = 1200000 ether; // used in transactions

// ================== Setup ==================

function setUp() public {
// Create the fork helper for mainnet and polygon
uint256[] memory chains = new uint256[](2);
chains[0] = 1;
chains[1] = 137;

uint256[] memory blocks = new uint256[](2);

FORK_HELPER = new ForkHelper(chains, blocks);
vm.makePersistent(address(FORK_HELPER));

// Create the forks
FORK_HELPER.utils_createForks();
assertEq(FORK_HELPER.utils_getNetworksCount(), 2, "!forks");
}

function utils_generateTransactions()
public
view
returns (Transaction[] memory _transactions)
{
// Generate executable from `Velodrome-transactions.json`
string memory path = string.concat(vm.projectRoot(), TRANSACTIONS_PATH);

string memory json = vm.readFile(path);

// Generate the bytes of the multisend transactions
_transactions = new Transaction[](NUMBER_TRANSACTIONS);
for (uint256 i; i < NUMBER_TRANSACTIONS; i++) {
string memory baseJsonPath = string.concat(
".transactions[",
i.toString(),
"]"
);
address to = json.readAddress(string.concat(baseJsonPath, ".to"));
uint256 value = json.readUint(
string.concat(baseJsonPath, ".value")
);
// No way to check if data is null in json, this will revert if data is null
// TODO: add support to automatically generate data if its null
bytes memory data = json.readBytes(
string.concat(baseJsonPath, ".data")
);

// Add to transactions
_transactions[i] = Transaction({
to: to,
value: value,
data: data,
operation: Operation.Call
});
}
}

function utils_getXCallTo(
uint256 transactionIdx
) public view returns (address _to) {
// Generate executable from `Velodrome-transactions.json`
string memory path = string.concat(vm.projectRoot(), TRANSACTIONS_PATH);

string memory json = vm.readFile(path);
string memory jsonPath = string.concat(
".transactions[",
transactionIdx.toString(),
"].contractInputsValues._to"
);
_to = json.readAddress(jsonPath);
}

// ================== Tests ==================
function test_executableShouldPass() public {
// Generate the multisend transactions
// bytes memory transactions = utils_generateMultisendTransactions();
Transaction[] memory transactions = utils_generateTransactions();

// Select and prep mainnet fork
vm.selectFork(FORK_HELPER.forkIdsByChain(1));
address caller = AddressLookup.getConnextDao(1);
uint256 initial = IERC20(AddressLookup.getNEXTAddress(1)).balanceOf(
caller
);
vm.makePersistent(caller);

// Submit the transactions
// NOTE: This assumes signatures will be valid, and the batching of these transactions
// will be valid. Simply pranks and calls each function in a loop as DAO.
for (uint256 i; i < transactions.length; i++) {
// Send tx
vm.prank(caller);
(bool success, ) = transactions[i].to.call(transactions[i].data);
assertTrue(success, string.concat("!success @ ", i.toString()));
}

// Select and prep Polygon fork
vm.selectFork(FORK_HELPER.forkIdsByChain(137));
caller = AddressLookup.getConnext(137);
vm.makePersistent(caller);

// Process polygon xcall for `approval` by transferring to `to`
address to = utils_getXCallTo(3);
address asset = AddressLookup.getNEXTAddress(137);
vm.startPrank(caller);
uint256 initialbalance = IERC20(AddressLookup.getNEXTAddress(137))
.balanceOf(to);

// Mint on NEXT to caller
IXERC20(asset).mint(to, LIQUIDITY_AMOUNT_POLYGON);
// No calldata on the xcall
vm.stopPrank();

// Ensure the polygon balance increased
uint256 balance = IERC20(AddressLookup.getNEXTAddress(137)).balanceOf(
to
);
assertEq(
balance,
LIQUIDITY_AMOUNT_POLYGON + initialbalance,
"!balance"
);

// Ensure the connext mainnet balance decreased
vm.selectFork(FORK_HELPER.forkIdsByChain(1));
assertEq(
IERC20(AddressLookup.getNEXTAddress(1)).balanceOf(
AddressLookup.getConnextDao(1)
),
initial - LIQUIDITY_AMOUNT_POLYGON,
"!balance"
);
}
}
Loading