-
Notifications
You must be signed in to change notification settings - Fork 1
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
f60605a
commit 077b734
Showing
2 changed files
with
296 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,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" | ||
} | ||
} | ||
] | ||
} |
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,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" | ||
); | ||
} | ||
} |