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

evm: add forge scripts for deployment #19

Open
wants to merge 6 commits 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
51 changes: 50 additions & 1 deletion evm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,53 @@ Build the sdk:
`cd ts-sdk && npm ci && npm run build`

Then configure `.env` with your private keys before finally running the scripts via `package.json`.



## Running forge-scripts

### Deployment

Before deploying the swap layer contract:

1. Open `cfg/evm.deployments.json`
2. Update the following in the configuration:
- Set addresses in the `deployment` array (currently `address(0)`)
- Configure any additional desired networks
- Ensure all target networks are properly configured before proceeding

> **Important**: The configuration file includes example entries with testnet Wormhole chain IDs. Forge requires dictionary keys to be in alphabetical order - do not modify this ordering.

To run the deployment script, run the following command from the `root`:
```
# Parameters:

# YOUR_RPC: Network RPC endpoint
# YOUR_PRIVATE_KEY: Deployment wallet private key
# WORMHOLE_CHAIN_ID: Target network's Wormhole chain ID

bash sh/deploy.sh -r YOUR_RPC -k YOUR_PRIVATE_KEY -c WORMHOLE_CHAIN_ID
```

Run this command for each network in the `evm.deployments.json` config. Make sure to save each address that is output in the forge logs. The [Configuration](#configuration) section relies on the `deployment` section of the config file, so please do not modify any values post deployment.

### Configuration

After deploying to each network, do the following:
1. Add each 32-byte proxy address to the `registrations` section of the `evm.deployments.json` file
2. Configure the relay parameters for each network. See the `FeeParams.sol` file for more information about these parameters

> **Important**: `gasDropoffMargin` and `gasPriceMargin` are both set using `fractionalDigits` of zero. Meaning, that if you set the `gasPriceMargin` parameter to `25` in this config, that will be 25%. The ConfigureSwapLayer.s.sol file will need to be updated to use different `fractionalDigits`.

To run the configuration script, run the following command from the `root`:

```
bash sh/deploy.sh -r YOUR_RPC -k YOUR_PRIVATE_KEY -c WORMHOLE_CHAIN_ID -a configure
```

### Validation

To confirm that the contract was configured according to the parameters specified in `evm.deployments.json`. Run the following command for each network:

```
bash sh/deploy.sh -r YOUR_RPC -k YOUR_PRIVATE_KEY -c WORMHOLE_CHAIN_ID -a validate
```
54 changes: 54 additions & 0 deletions evm/cfg/evm.deployment.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"deployment": [
{
"assistant": "0x0000000000000000000000000000000000000000",
"chainId": 6,
"circleDomain": 1,
"circleMessageTransmitter": "0x0000000000000000000000000000000000000000",
"feeRecipient": "0x0000000000000000000000000000000000000000",
"feeUpdater": "0x0000000000000000000000000000000000000000",
"liquidityLayerAddress": "0x0000000000000000000000000000000000000000",
"permit2Address": "0x0000000000000000000000000000000000000000",
"traderJoeRouterAddress": "0x0000000000000000000000000000000000000000",
"universalRouterAddress": "0x0000000000000000000000000000000000000000",
"wethAddress": "0x0000000000000000000000000000000000000000",
"wormholeAddress": "0x0000000000000000000000000000000000000000"
},
{
"assistant": "0x0000000000000000000000000000000000000000",
"chainId": 10003,
"circleDomain": 3,
"circleMessageTransmitter": "0x0000000000000000000000000000000000000000",
"feeRecipient": "0x0000000000000000000000000000000000000000",
"feeUpdater": "0x0000000000000000000000000000000000000000",
"liquidityLayerAddress": "0x0000000000000000000000000000000000000000",
"permit2Address": "0x0000000000000000000000000000000000000000",
"traderJoeRouterAddress": "0x0000000000000000000000000000000000000000",
"universalRouterAddress": "0x0000000000000000000000000000000000000000",
"wethAddress": "0x0000000000000000000000000000000000000000",
"wormholeAddress": "0x0000000000000000000000000000000000000000"
}
],
"registrations": [
{
"baseFee": 250000,
"chainId": 6,
"gasDropoffMargin": 1,
"gasPrice": 250000000000,
"gasPriceMargin": 25,
"gasTokenPrice": 100000000,
"maxGasDropoff": 100000000000000000,
"swapLayerAddress": "0x0000000000000000000000000000000000000000000000000000000000000000"
},
{
"baseFee": 250000,
"chainId": 10003,
"gasDropoffMargin": 1,
"gasPrice": 250000000000,
"gasPriceMargin": 25,
"gasTokenPrice": 100000000,
"maxGasDropoff": 100000000000000000,
"swapLayerAddress": "0x0000000000000000000000000000000000000000000000000000000000000000"
}
]
}
53 changes: 53 additions & 0 deletions evm/forge-scripts/ConfigureSwapLayer.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: Apache 2

pragma solidity ^0.8.24;

import "forge-std/console2.sol";

import "wormhole-sdk/proxy/Proxy.sol";
import {SwapLayer} from "swap-layer/SwapLayer.sol";

import {ParseSwapLayerConfig} from "./utils/ParseSLConfig.sol";

import "swap-layer/assets/SwapLayerRelayingFees.sol";
import "swap-layer/assets/SwapLayerGovernance.sol";

contract ConfigureSwapLayerForTest is ParseSwapLayerConfig {
function createPeerRegistrationCommand(SLRegistration memory config) internal pure returns (bytes memory) {
FeeParams feeParams;
feeParams = feeParams.baseFee(config.baseFee);
feeParams = feeParams.gasPrice(GasPriceLib.to(config.gasPrice));
feeParams = feeParams.gasPriceMargin(PercentageLib.to(config.gasPriceMargin, 0));
feeParams = feeParams.maxGasDropoff(GasDropoffLib.to(config.maxGasDropoff));
feeParams = feeParams.gasDropoffMargin(PercentageLib.to(config.gasDropoffMargin, 0));
feeParams = feeParams.gasTokenPrice(config.gasTokenPrice);

return abi.encodePacked(GovernanceCommand.UpdatePeer, config.chainId, config.swapLayer, feeParams);
}

function run() public {
vm.startBroadcast();

// Wormhole chain ID that we are configuring.
uint16 thisChainId = uint16(vm.envUint("RELEASE_WORMHOLE_CHAIN_ID"));

(SLRegistration[] memory config, SwapLayer swapLayer) = _parseRegistrationConfig(thisChainId);

bytes memory governanceCommands;
for (uint256 i = 0; i < config.length; i++) {
// Ignore our own chain ID.
if (config[i].chainId == thisChainId) {
continue;
}

governanceCommands = abi.encodePacked(governanceCommands, createPeerRegistrationCommand(config[i]));
}

// Batch the governance commands now.
swapLayer.batchGovernanceCommands(governanceCommands);

console2.log("Successfully configured swap layer for chain ID:", thisChainId);

vm.stopBroadcast();
}
}
42 changes: 42 additions & 0 deletions evm/forge-scripts/DeploySwapLayer.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: Apache 2

pragma solidity ^0.8.24;

import "forge-std/console2.sol";

import "wormhole-sdk/proxy/Proxy.sol";
import {SwapLayer} from "swap-layer/SwapLayer.sol";

import {ParseSwapLayerConfig} from "./utils/ParseSLConfig.sol";

contract DeploySwapLayerForTest is ParseSwapLayerConfig {
function deploy(DeploymentConfig memory config) public {
address swapLayer = address(
new Proxy(
address(
new SwapLayer(
config.liquidityLayer,
config.permit2,
config.weth,
config.universalRouter,
config.traderJoeRouter
)
),
abi.encodePacked(msg.sender, config.assistant, config.feeUpdater, config.feeRecipient)
)
);

console2.log("Swap layer proxy deployed at:", swapLayer);
return;
}

function run() public {
vm.startBroadcast();

DeploymentConfig memory config =
_parseAndValidateDeploymentConfig(uint16(vm.envUint("RELEASE_WORMHOLE_CHAIN_ID")));

deploy(config);
vm.stopBroadcast();
}
}
142 changes: 142 additions & 0 deletions evm/forge-scripts/ValidateSwapLayer.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// SPDX-License-Identifier: Apache 2

pragma solidity ^0.8.24;

import "forge-std/console2.sol";

import "wormhole-sdk/proxy/Proxy.sol";
import "wormhole-sdk/libraries/BytesParsing.sol";
import {SwapLayer} from "swap-layer/SwapLayer.sol";

import {ParseSwapLayerConfig} from "./utils/ParseSLConfig.sol";

import "swap-layer/assets/SwapLayerRelayingFees.sol";
import "swap-layer/assets/SwapLayerGovernance.sol";
import "swap-layer/assets/SwapLayerQuery.sol";

contract ValidateSwapLayerForTest is ParseSwapLayerConfig {
using BytesParsing for bytes;

function validatePeers(uint16 wormholeChainId, SwapLayer swapLayer, SLRegistration[] memory registrations)
internal
view
{
// Loop through each registration and validate the peer.
for (uint256 i = 0; i < registrations.length; i++) {
SLRegistration memory registration = registrations[i];
if (registration.chainId == wormholeChainId) {
// Skip self registration.
continue;
}

bytes memory getRes = swapLayer.batchQueries(abi.encodePacked(QueryType.Peer, registration.chainId));
(bytes32 peer,) = getRes.asBytes32Unchecked(0);
require(peer == registration.swapLayer, "Peer mismatch");
}
}

function validateFeeParams(uint16 wormholeChainId, SwapLayer swapLayer, SLRegistration[] memory registrations)
internal
view
{
// Loop through each registration and validate the fee params.
for (uint256 i = 0; i < registrations.length; i++) {
SLRegistration memory registration = registrations[i];
if (registration.chainId == wormholeChainId) {
// Skip self registration.
continue;
}

bytes memory getRes = swapLayer.batchQueries(abi.encodePacked(QueryType.FeeParams, registration.chainId));
(uint256 queriedFeeParams,) = getRes.asUint256Unchecked(0);

// Parse the fee params.
FeeParams feeParams;
feeParams = feeParams.baseFee(registration.baseFee);
feeParams = feeParams.gasPrice(GasPriceLib.to(registration.gasPrice));
feeParams = feeParams.gasPriceMargin(PercentageLib.to(registration.gasPriceMargin, 0));
feeParams = feeParams.maxGasDropoff(GasDropoffLib.to(registration.maxGasDropoff));
feeParams = feeParams.gasDropoffMargin(PercentageLib.to(registration.gasDropoffMargin, 0));
feeParams = feeParams.gasTokenPrice(registration.gasTokenPrice);

require(FeeParams.unwrap(feeParams) == queriedFeeParams, "Fee params mismatch");
}
}

function validateDeploymentAndConfiguration(
uint16 wormholeChainId,
SwapLayer swapLayer,
DeploymentConfig memory deployConfig,
SLRegistration[] memory registrations
) internal view {
bytes memory getRes = swapLayer.batchQueries(
abi.encodePacked(
QueryType.Owner,
QueryType.PendingOwner,
QueryType.FeeRecipient,
QueryType.FeeUpdater,
QueryType.Assistant
)
);
(address owner,) = getRes.asAddressUnchecked(0);
(address pendingOwner,) = getRes.asAddressUnchecked(20);
(address feeRecipient,) = getRes.asAddressUnchecked(40);
(address feeUpdater,) = getRes.asAddressUnchecked(60);
(address assistant,) = getRes.asAddressUnchecked(80);

require(owner == msg.sender, "Owner mismatch");
require(pendingOwner == address(0), "Pending owner mismatch");
require(feeRecipient == deployConfig.feeRecipient, "Fee recipient mismatch");
require(feeUpdater == deployConfig.feeUpdater, "Fee updater mismatch");
require(assistant == deployConfig.assistant, "Assistant mismatch");

validatePeers(wormholeChainId, swapLayer, registrations);

validateFeeParams(wormholeChainId, swapLayer, registrations);

getRes = swapLayer.batchQueries(
abi.encodePacked(
QueryType.Immutable,
ImmutableType.Wormhole,
QueryType.Immutable,
ImmutableType.WrappedNative,
QueryType.Immutable,
ImmutableType.Permit2,
QueryType.Immutable,
ImmutableType.UniswapRouter,
QueryType.Immutable,
ImmutableType.TraderJoeRouter,
QueryType.Immutable,
ImmutableType.LiquidityLayer
)
);

(address wormhole,) = getRes.asAddressUnchecked(0);
(address weth,) = getRes.asAddressUnchecked(20);
(address permit2,) = getRes.asAddressUnchecked(40);
(address universalRouter,) = getRes.asAddressUnchecked(60);
(address traderJoe,) = getRes.asAddressUnchecked(80);
(address liquidity,) = getRes.asAddressUnchecked(100);

require(wormhole == deployConfig.wormhole, "Wormhole mismatch");
require(weth == deployConfig.weth, "WETH mismatch");
require(permit2 == deployConfig.permit2, "Permit2 mismatch");
require(universalRouter == deployConfig.universalRouter, "Uniswap router mismatch");
require(traderJoe == deployConfig.traderJoeRouter, "Trader Joe router mismatch");
require(liquidity == deployConfig.liquidityLayer, "Liquidity layer mismatch");
}

function run() public {
vm.startBroadcast();

// Wormhole chain ID that we are configuring.
uint16 thisChainId = uint16(vm.envUint("RELEASE_WORMHOLE_CHAIN_ID"));

DeploymentConfig memory deployConfig = _parseAndValidateDeploymentConfig(thisChainId);
(SLRegistration[] memory registrations, SwapLayer swapLayer) = _parseRegistrationConfig(thisChainId);

validateDeploymentAndConfiguration(thisChainId, swapLayer, deployConfig, registrations);

vm.stopBroadcast();
}
}
Loading
Loading