Merge branch 'main' into usx/tnl/ENG-4668-naive-callback

This commit is contained in:
Tamara
2025-07-09 08:06:57 -04:00
committed by GitHub
12 changed files with 3660 additions and 401 deletions

View File

@@ -1,3 +1,15 @@
## [0.105.0](https://github.com/propeller-heads/tycho-execution/compare/0.104.0...0.105.0) (2025-07-09)
### Features
* Upgrade scripts to submit to Safe wallet ([2733bb0](https://github.com/propeller-heads/tycho-execution/commit/2733bb00724fb2df8d6f8151df02826807289c9a))
### Bug Fixes
* Simplify nonceOffset logic ([ba60e4b](https://github.com/propeller-heads/tycho-execution/commit/ba60e4bb73741c17c44e1cfcdea3fd599ae027eb))
## [0.104.0](https://github.com/propeller-heads/tycho-execution/compare/0.103.0...0.104.0) (2025-07-08)

2
Cargo.lock generated
View File

@@ -4658,7 +4658,7 @@ dependencies = [
[[package]]
name = "tycho-execution"
version = "0.104.0"
version = "0.105.0"
dependencies = [
"alloy",
"chrono",

View File

@@ -1,6 +1,6 @@
[package]
name = "tycho-execution"
version = "0.104.0"
version = "0.105.0"
edition = "2021"
description = "Provides tools for encoding and executing swaps against Tycho router and protocol executors."
repository = "https://github.com/propeller-heads/tycho-execution"

3793
foundry/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,9 @@
},
"dependencies": {
"@nomicfoundation/hardhat-foundry": "^1.1.3",
"ethers": "^5.0.0",
"@safe-global/api-kit": "^1.1.0",
"@safe-global/protocol-kit": "^1.0.1",
"ethers": "^5.8.0",
"prompt-sync": "^4.2.0"
}
}

View File

@@ -53,4 +53,17 @@ For each of the following, you must select one of `tenderly_ethereum`, `tenderly
1. If you set a new executor for the same protocol, you need to remove the old one.
2. Run: `npx hardhat run scripts/remove-executor.js --network NETWORK`
3. There will be a prompt for you to insert the executor address you want to remove.
3. There will be a prompt for you to insert the executor address you want to remove.
### Revoke roles
1. If you wish to revoke a role for a certain address, run: `npx hardhat run scripts/revoke-role.js --network NETWORK`
2. There will be a prompt for you to insert the role hash and the address you want to revoke it for.
### Safe wallet
1. If the wallet that has the role, is a Gnosis Safe, you need to set the `SAFE_ADDRESS` env var.
2. The scripts deploy-executors, remove-executor, set-roles and revoke-role all support this.
1. If `SAFE_ADDRESS` is set, then it will propose a transaction to the safe wallet and later on it needs to be
approved in their UI to execute on chain.
2. If it's not set, it will submit the transaction directly to the chain.

View File

@@ -1,16 +1,22 @@
require('dotenv').config();
const {ethers} = require("hardhat");
const hre = require("hardhat");
const {proposeOrSendTransaction} = require("./utils");
const prompt = require('prompt-sync')();
async function main() {
const network = hre.network.name;
const routerAddress = process.env.ROUTER_ADDRESS;
console.log(`Removing executors on TychoRouter at ${routerAddress} on ${network}`);
const safeAddress = process.env.SAFE_ADDRESS;
if (!routerAddress) {
throw new Error("Missing ROUTER_ADDRESS");
}
const [deployer] = await ethers.getSigners();
console.log(`Removing executors with account: ${deployer.address}`);
console.log(`Account balance: ${ethers.utils.formatEther(await deployer.getBalance())} ETH`);
console.log(`Removing executor on TychoRouter at ${routerAddress} on ${network}`);
const [signer] = await ethers.getSigners();
console.log(`Removing executors with account: ${signer.address}`);
console.log(`Account balance: ${ethers.utils.formatEther(await signer.getBalance())} ETH`);
const TychoRouter = await ethers.getContractFactory("TychoRouter");
const router = TychoRouter.attach(routerAddress);
@@ -22,12 +28,15 @@ async function main() {
process.exit(1);
}
// Remove executor
const tx = await router.removeExecutor(executorAddress, {
const txData = {
to: router.address,
data: router.interface.encodeFunctionData("removeExecutor", [executorAddress]),
value: "0",
gasLimit: 50000
});
await tx.wait(); // Wait for the transaction to be mined
console.log(`Executor removed at transaction: ${tx.hash}`);
};
const txHash = await proposeOrSendTransaction(safeAddress, txData, signer, "removeExecutor");
console.log(`TX hash: ${txHash}`);
}
main()

View File

@@ -0,0 +1,51 @@
require('dotenv').config();
const {ethers} = require("hardhat");
const path = require('path');
const fs = require('fs');
const hre = require("hardhat");
const {proposeOrSendTransaction} = require("./utils");
const prompt = require('prompt-sync')();
async function main() {
const network = hre.network.name;
const routerAddress = process.env.ROUTER_ADDRESS;
const safeAddress = process.env.SAFE_ADDRESS;
if (!routerAddress) {
throw new Error("Missing ROUTER_ADDRESS");
}
console.log(`Revoking role on TychoRouter at ${routerAddress} on ${network}`);
const [signer] = await ethers.getSigners();
console.log(`Setting roles with account: ${signer.address}`);
console.log(`Account balance: ${ethers.utils.formatEther(await signer.getBalance())} ETH`);
const TychoRouter = await ethers.getContractFactory("TychoRouter");
const router = TychoRouter.attach(routerAddress);
const roleHash = prompt("Enter role hash to be removed: ");
const address = prompt("Enter the address to remove: ");
if (!roleHash || !address) {
console.error("Please provide the executorAddress as an argument.");
process.exit(1);
}
console.log(`Revoking ${roleHash} to the following address:`, address);
const txData = {
to: router.address,
data: router.interface.encodeFunctionData("revokeRole", [roleHash, address]),
value: "0",
};
const txHash = await proposeOrSendTransaction(safeAddress, txData, signer, "revokeRole");
console.log(`TX hash: ${txHash}`);
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error("Error setting roles:", error);
process.exit(1);
});

View File

@@ -1,44 +1,50 @@
{
"ethereum": {
"EXECUTOR_SETTER_ROLE": [
"0x58Dc7Bf9eD1f4890A7505D5bE4E4252978eAF655"
"0x06e580B872a37402764f909FCcAb0Eb5bb38fe23"
],
"PAUSER_ROLE": [
"0x58Dc7Bf9eD1f4890A7505D5bE4E4252978eAF655"
"0xB279A562C726F9F3011c1945c9c23Fe1FB631B59",
"0xAC3649A6DFBBB230632604f2fc43773977ec6E67"
],
"UNPAUSER_ROLE": [
"0x58Dc7Bf9eD1f4890A7505D5bE4E4252978eAF655"
"0xB279A562C726F9F3011c1945c9c23Fe1FB631B59",
"0xAC3649A6DFBBB230632604f2fc43773977ec6E67"
],
"FUND_RESCUER_ROLE": [
"0x58Dc7Bf9eD1f4890A7505D5bE4E4252978eAF655"
"0xF621770E96bcf1335150faecf77D757faf7ca4A9"
]
},
"base": {
"EXECUTOR_SETTER_ROLE": [
"0x58Dc7Bf9eD1f4890A7505D5bE4E4252978eAF655"
"0x06e580B872a37402764f909FCcAb0Eb5bb38fe23"
],
"PAUSER_ROLE": [
"0x58Dc7Bf9eD1f4890A7505D5bE4E4252978eAF655"
"0xB279A562C726F9F3011c1945c9c23Fe1FB631B59",
"0xAC3649A6DFBBB230632604f2fc43773977ec6E67"
],
"UNPAUSER_ROLE": [
"0x58Dc7Bf9eD1f4890A7505D5bE4E4252978eAF655"
"0xB279A562C726F9F3011c1945c9c23Fe1FB631B59",
"0xAC3649A6DFBBB230632604f2fc43773977ec6E67"
],
"FUND_RESCUER_ROLE": [
"0x58Dc7Bf9eD1f4890A7505D5bE4E4252978eAF655"
"0xF621770E96bcf1335150faecf77D757faf7ca4A9"
]
},
"unichain": {
"EXECUTOR_SETTER_ROLE": [
"0x58Dc7Bf9eD1f4890A7505D5bE4E4252978eAF655"
"0x06e580B872a37402764f909FCcAb0Eb5bb38fe23"
],
"PAUSER_ROLE": [
"0x58Dc7Bf9eD1f4890A7505D5bE4E4252978eAF655"
"0xB279A562C726F9F3011c1945c9c23Fe1FB631B59",
"0xAC3649A6DFBBB230632604f2fc43773977ec6E67"
],
"UNPAUSER_ROLE": [
"0x58Dc7Bf9eD1f4890A7505D5bE4E4252978eAF655"
"0xB279A562C726F9F3011c1945c9c23Fe1FB631B59",
"0xAC3649A6DFBBB230632604f2fc43773977ec6E67"
],
"FUND_RESCUER_ROLE": [
"0x58Dc7Bf9eD1f4890A7505D5bE4E4252978eAF655"
"0xF621770E96bcf1335150faecf77D757faf7ca4A9"
]
}
}

View File

@@ -1,18 +1,25 @@
require('dotenv').config();
const {ethers} = require("hardhat");
const path = require('path');
const fs = require('fs');
const hre = require("hardhat");
const path = require('path');
const {proposeOrSendTransaction} = require("./utils");
const prompt = require('prompt-sync')();
async function main() {
const network = hre.network.name;
const routerAddress = process.env.ROUTER_ADDRESS;
const safeAddress = process.env.SAFE_ADDRESS;
if (!routerAddress) {
throw new Error("Missing ROUTER_ADDRESS");
}
console.log(`Setting executors on TychoRouter at ${routerAddress} on ${network}`);
const [deployer] = await ethers.getSigners();
console.log(`Setting executors with account: ${deployer.address}`);
console.log(`Account balance: ${ethers.utils.formatEther(await deployer.getBalance())} ETH`);
const [signer] = await ethers.getSigners();
const balance = await signer.getBalance();
console.log(`Using signer: ${signer.address}`);
console.log(`Account balance: ${ethers.utils.formatEther(balance)} ETH`);
const TychoRouter = await ethers.getContractFactory("TychoRouter");
const router = TychoRouter.attach(routerAddress);
@@ -48,13 +55,16 @@ async function main() {
return;
}
// Set executors
const executorAddresses = executorsToSet.map(executor => executor.executor);
const tx = await router.setExecutors(executorAddresses, {
gasLimit: 300000 // should be around 50k per executor
});
await tx.wait(); // Wait for the transaction to be mined
console.log(`Executors set at transaction: ${tx.hash}`);
const executorAddresses = executorsToSet.map(({executor}) => executor);
const txData = {
to: router.address,
data: router.interface.encodeFunctionData("setExecutors", [executorAddresses]),
value: "0",
gasLimit: 300000
};
const txHash = await proposeOrSendTransaction(safeAddress, txData, signer, "setExecutors");
console.log(`TX hash: ${txHash}`);
}
main()

View File

@@ -3,15 +3,21 @@ const {ethers} = require("hardhat");
const path = require('path');
const fs = require('fs');
const hre = require("hardhat");
const {proposeOrSendTransaction} = require("./utils");
async function main() {
const network = hre.network.name;
const routerAddress = process.env.ROUTER_ADDRESS;
const safeAddress = process.env.SAFE_ADDRESS;
if (!routerAddress) {
throw new Error("Missing ROUTER_ADDRESS");
}
console.log(`Setting roles on TychoRouter at ${routerAddress} on ${network}`);
const [deployer] = await ethers.getSigners();
console.log(`Setting roles with account: ${deployer.address}`);
console.log(`Account balance: ${ethers.utils.formatEther(await deployer.getBalance())} ETH`);
const [signer] = await ethers.getSigners();
console.log(`Setting roles with account: ${signer.address}`);
console.log(`Account balance: ${ethers.utils.formatEther(await signer.getBalance())} ETH`);
const TychoRouter = await ethers.getContractFactory("TychoRouter");
const router = TychoRouter.attach(routerAddress);
@@ -20,7 +26,6 @@ async function main() {
const roles = {
EXECUTOR_SETTER_ROLE: "0x6a1dd52dcad5bd732e45b6af4e7344fa284e2d7d4b23b5b09cb55d36b0685c87",
FEE_SETTER_ROLE: "0xe6ad9a47fbda1dc18de1eb5eeb7d935e5e81b4748f3cfc61e233e64f88182060",
PAUSER_ROLE: "0x65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a",
UNPAUSER_ROLE: "0x427da25fe773164f88948d3e215c94b6554e2ed5e5f203a821c9f2f6131cf75a",
FUND_RESCUER_ROLE: "0x912e45d663a6f4cc1d0491d8f046e06c616f40352565ea1cdb86a0e1aaefa41b"
@@ -31,9 +36,15 @@ async function main() {
const addresses = rolesDict[network][roleName];
if (addresses && addresses.length > 0) {
console.log(`Granting ${roleName} to the following addresses:`, addresses);
const tx = await router.batchGrantRole(roleHash, addresses);
await tx.wait(); // Wait for the transaction to be mined
console.log(`Role ${roleName} granted at transaction: ${tx.hash}`);
const txData = {
to: router.address,
data: router.interface.encodeFunctionData("batchGrantRole", [roleHash, addresses]),
value: "0",
};
const txHash = await proposeOrSendTransaction(safeAddress, txData, signer, "batchGrantRole");
console.log(`Role ${roleName} granted at TX hash: ${txHash}`);
} else {
console.log(`No addresses found for role ${roleName}`);
}

66
foundry/scripts/utils.js Normal file
View File

@@ -0,0 +1,66 @@
const {ethers} = require("hardhat");
const Safe = require('@safe-global/protocol-kit').default;
const {EthersAdapter} = require('@safe-global/protocol-kit');
const {default: SafeApiKit} = require("@safe-global/api-kit");
const txServiceUrls = {
mainnet: "https://safe-transaction-mainnet.safe.global",
base: "https://safe-transaction-base.safe.global",
unichain: "https://safe-transaction-unichain.safe.global",
};
const txServiceUrl = txServiceUrls[hre.network.name];
async function proposeOrSendTransaction(safeAddress, txData, signer, methodName) {
if (safeAddress) {
return proposeTransaction(safeAddress, txData, signer, methodName);
} else {
console.log(`Executing the transaction directly`);
const tx = await signer.sendTransaction(txData);
await tx.wait();
return tx.hash;
}
}
async function proposeTransaction(safeAddress, txData, signer, methodName) {
const signerAddress = await signer.getAddress();
console.log(`Proposing transaction to Safe: ${safeAddress} with account: ${signerAddress}`);
const ethAdapter = new EthersAdapter({
ethers,
signerOrProvider: signer,
});
const safeService = new SafeApiKit({txServiceUrl, ethAdapter});
const safeSdk = await Safe.create({
ethAdapter,
safeAddress,
});
let next_nonce = await safeService.getNextNonce(safeAddress);
const safeTransaction = await safeSdk.createTransaction({
safeTransactionData: {
...txData,
nonce: next_nonce
}
});
const safeTxHash = await safeSdk.getTransactionHash(safeTransaction);
const senderSignature = await safeSdk.signTransactionHash(safeTxHash);
const proposeArgs = {
safeAddress,
safeTransactionData: safeTransaction.data,
safeTxHash,
senderAddress: signerAddress,
senderSignature: senderSignature.data,
origin: `Proposed from hardhat: ${methodName}`,
nonce: next_nonce,
};
await safeService.proposeTransaction(proposeArgs);
return safeTxHash;
}
module.exports = {
proposeOrSendTransaction
}