Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
aec5398
note the variables to update; set the initial merkle root
saucepoint Oct 23, 2025
08b18bc
initial fee values
saucepoint Oct 23, 2025
ff46547
Merge branch 'main' into deployer-update
saucepoint Oct 29, 2025
c0b3fe5
use real root and proofs in Deployer fork test
saucepoint Oct 29, 2025
cfa2d8f
Merge branch 'main' into deployer-update
saucepoint Oct 30, 2025
0c6edb8
assert initial fee state
saucepoint Oct 30, 2025
4c9baac
updated default fee values
saucepoint Nov 7, 2025
ecf5b83
Merge branch 'main' into deployer-update
marktoda Nov 13, 2025
95c7cca
feat: add Unification proposal script
marktoda Nov 13, 2025
c7dd6d6
Merge branch 'main' into deployer-update
marktoda Nov 14, 2025
736ebc4
feat: add UNIVesting to deployer
marktoda Nov 14, 2025
79f7e84
feat: add Vesting fork tests
marktoda Nov 14, 2025
212f4b5
feat: add Unichain deployers
marktoda Nov 14, 2025
3906a4a
feat: cleanup
marktoda Nov 14, 2025
3f084e7
fix: deployer test
marktoda Nov 17, 2025
275679e
fix: Unichain rpc url in ci
marktoda Nov 17, 2025
e608700
fix: update proposal comments
marktoda Nov 18, 2025
b8d5546
feat: set labs recipient
marktoda Nov 18, 2025
d71415a
fix: sara suggestions
marktoda Nov 20, 2025
11a9e7a
feat: broadcast in 01 and 02
marktoda Nov 21, 2025
9ab5b18
fix: lint
marktoda Nov 21, 2025
4a36b5a
add attestations and calldata generation to proposal script
wildmolasses Nov 18, 2025
0a1fcc6
add agreement anchor creation script (#2)
wildmolasses Nov 21, 2025
8d2832d
test script
wildmolasses Nov 21, 2025
ffe2419
rename scripts for cleaner diff
wildmolasses Nov 21, 2025
c78405c
Merge pull request #100 from ScopeLift/add-attestations
marktoda Nov 21, 2025
3179995
feat: lower thresholds
marktoda Nov 21, 2025
95b8fc7
feat: cleanup agreement anchors
marktoda Nov 21, 2025
89b4030
feat: use EAS library
marktoda Nov 21, 2025
665fd40
merge in main; fix conflicts
saucepoint Nov 21, 2025
09e2a05
copy over natspec from old deployer
saucepoint Nov 21, 2025
00e9cc4
strengthen insufficient UNI test
saucepoint Nov 21, 2025
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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,4 @@ jobs:
FOUNDRY_PROFILE: pr
FORGE_SNAPSHOT_CHECK: true
MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }}
UNICHAIN_RPC_URL: ${{ secrets.UNICHAIN_RPC_URL }}
54 changes: 28 additions & 26 deletions foundry.toml
Original file line number Diff line number Diff line change
@@ -1,39 +1,41 @@
[profile.default]
evm_version = "cancun"
optimizer = true
optimizer_runs = 10_000_000
solc_version = "0.8.29"
verbosity = 3
gas_limit = "9223372036854775807"
no_match_test = "test_fuzz_gas_release_malicious"
evm_version = "cancun"
optimizer = true
optimizer_runs = 10_000_000
solc_version = "0.8.29"
verbosity = 3
gas_limit = "9223372036854775807"
no_match_test = "test_fuzz_gas_release_malicious"

[rpc_endpoints]
mainnet = "${MAINNET_RPC_URL}"
mainnet = "${MAINNET_RPC_URL}"
unichain = "${UNICHAIN_RPC_URL}"

[profile.ci]
fuzz = { runs = 5000 }
invariant = { runs = 1000 }
fuzz = { runs = 5000 }
invariant = { runs = 1000 }

[profile.coverage]
fuzz = { runs = 100 }
invariant = { runs = 0 }
fuzz = { runs = 100 }
invariant = { runs = 0 }

[profile.lite]
fuzz = { runs = 50 }
invariant = { runs = 10 }
# Speed up compilation and tests during development.
optimizer = false
fuzz = { runs = 50 }
invariant = { runs = 10 }
# Speed up compilation and tests during development.
optimizer = false

[fmt]
bracket_spacing = false
int_types = "long"
line_length = 100
multiline_func_header = "attributes_first"
number_underscore = "thousands"
quote_style = "double"
single_line_statement_blocks = "single"
tab_width = 2
wrap_comments = true
bracket_spacing = false
int_types = "long"
line_length = 100
multiline_func_header = "attributes_first"
number_underscore = "thousands"
quote_style = "double"
single_line_statement_blocks = "single"
tab_width = 2
wrap_comments = true

[lint]
exclude_lints = ["mixed-case-variable", "mixed-case-function"]
exclude_lints = ["mixed-case-variable", "mixed-case-function"]

21 changes: 21 additions & 0 deletions script/01_DeployMainnet.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {console2} from "forge-std/console2.sol";
import "forge-std/Script.sol";
import {MainnetDeployer} from "./deployers/MainnetDeployer.sol";

contract DeployMainnet is Script {
function setUp() public {}

function run() public {
require(block.chainid == 1, "Not mainnet");

MainnetDeployer deployer = new MainnetDeployer();
console2.log("Deployed Deployer at:", address(deployer));
console2.log("TOKEN_JAR at:", address(deployer.TOKEN_JAR()));
console2.log("RELEASER at:", address(deployer.RELEASER()));
console2.log("V3_FEE_ADAPTER at:", address(deployer.V3_FEE_ADAPTER()));
console2.log("UNI_VESTING at:", address(deployer.UNI_VESTING()));
}
}
22 changes: 22 additions & 0 deletions script/02_DeployUnichain.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {console2} from "forge-std/console2.sol";
import "forge-std/Script.sol";
import {UnichainDeployer} from "./deployers/UnichainDeployer.sol";
import {ITokenJar} from "../src/interfaces/ITokenJar.sol";
import {TokenJar} from "../src/TokenJar.sol";
import {OptimismBridgedResourceFirepit} from "../src/releasers/OptimismBridgedResourceFirepit.sol";

contract DeployUnichain is Script {
function setUp() public {}

function run() public {
require(block.chainid == 130, "Not Unichain");

UnichainDeployer deployer = new UnichainDeployer();
console2.log("Deployed Deployer at:", address(deployer));
console2.log("TOKEN_JAR at:", address(deployer.TOKEN_JAR()));
console2.log("RELEASER at:", address(deployer.RELEASER()));
}
}
54 changes: 54 additions & 0 deletions script/03_UnificationProposal.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "forge-std/Script.sol";
import {MainnetDeployer} from "./deployers/MainnetDeployer.sol";
import {IUniswapV2Factory} from "briefcase/protocols/v2-core/interfaces/IUniswapV2Factory.sol";
import {IUniswapV3Factory} from "briefcase/protocols/v3-core/interfaces/IUniswapV3Factory.sol";
import {IERC20} from "forge-std/interfaces/IERC20.sol";

contract UnificationProposal is Script {
IERC20 UNI = IERC20(0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984);
IUniswapV2Factory public V2_FACTORY =
IUniswapV2Factory(0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f);
IUniswapV3Factory public constant V3_FACTORY =
IUniswapV3Factory(0x1F98431c8aD98523631AE4a59f267346ea31F984);
address public constant OLD_FEE_TO_SETTER = 0x18e433c7Bf8A2E1d0197CE5d8f9AFAda1A771360;

function setUp() public {}

function run(MainnetDeployer deployer) public {
vm.startBroadcast();
_run(deployer);
vm.stopBroadcast();
}

function runPranked(MainnetDeployer deployer) public {
vm.startPrank(V3_FACTORY.owner());
_run(deployer);
vm.stopPrank();
}

function _run(MainnetDeployer deployer) public {
address timelock = deployer.V3_FACTORY().owner();

// Burn 100M UNI
UNI.transfer(address(0xdead), 100_000_000 ether);
/// Set the owner of the v3 factory to the configured fee controller
V3_FACTORY.setOwner(address(deployer.V3_FEE_ADAPTER()));
/// Update the v2 fee to setter to the timelock
IFeeToSetter(OLD_FEE_TO_SETTER).setFeeToSetter(timelock);
/// Set the recipient of v2 protocol fees to the token jar
V2_FACTORY.setFeeTo(address(deployer.TOKEN_JAR()));
/// Approve two years of vesting to the UNIvester smart contract UNI stays in treasury until
/// vested and unvested UNI can be cancelled by setting approve back to 0
UNI.approve(address(deployer.UNI_VESTING()), 40_000_000 ether);
}
}

// interface for:
// https://etherscan.io/address/0x18e433c7Bf8A2E1d0197CE5d8f9AFAda1A771360#code
// the current V2_FACTORY.feeToSetter()
interface IFeeToSetter {
function setFeeToSetter(address) external;
}
119 changes: 119 additions & 0 deletions script/deployers/MainnetDeployer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.29;

import {V3FeeAdapter} from "../../src/feeAdapters/V3FeeAdapter.sol";
import {ITokenJar} from "../../src/interfaces/ITokenJar.sol";
import {TokenJar} from "../../src/TokenJar.sol";
import {Firepit} from "../../src/releasers/Firepit.sol";
import {IUNIVesting} from "../../src/interfaces/IUNIVesting.sol";
import {UNIVesting} from "../../src/UNIVesting.sol";
import {IReleaser} from "../../src/interfaces/IReleaser.sol";
import {IV3FeeAdapter} from "../../src/interfaces/IV3FeeAdapter.sol";
import {IOwned} from "../../src/interfaces/base/IOwned.sol";
import {IUniswapV3Factory} from "v3-core/contracts/interfaces/IUniswapV3Factory.sol";

contract MainnetDeployer {
ITokenJar public immutable TOKEN_JAR;
IReleaser public immutable RELEASER;
IV3FeeAdapter public immutable V3_FEE_ADAPTER;
IUNIVesting public immutable UNI_VESTING;

address public constant RESOURCE = 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984;
uint256 public constant THRESHOLD = 10_000e18;
IUniswapV3Factory public constant V3_FACTORY =
IUniswapV3Factory(0x1F98431c8aD98523631AE4a59f267346ea31F984);
// TODO: set with the real UNI recipient when ready
address public constant LABS_UNI_RECIPIENT = 0xaBA63748c4b4DeF4a3319C3A29fE4829029D926F;

// Using the real merkle root from the generated merkle tree in ./merkle-generator
// TODO: Regenerate the merkle tree
bytes32 constant INITIAL_MERKLE_ROOT =
bytes32(0x472c8960ea78de635eb7e32c5085f9fb963e626b5a68c939bfad24e022383b3a);

uint8 constant DEFAULT_FEE_100 = 4 << 4 | 4; // default fee for 0.01% tier
uint8 constant DEFAULT_FEE_500 = 4 << 4 | 4; // default fee for 0.05% tier
uint8 constant DEFAULT_FEE_3000 = 6 << 4 | 6; // default fee for 0.3% tier
uint8 constant DEFAULT_FEE_10000 = 6 << 4 | 6; // default fee for 1% tier

bytes32 constant SALT_TOKEN_JAR = bytes32(uint256(1));
bytes32 constant SALT_RELEASER = bytes32(uint256(2));
bytes32 constant SALT_V3_FEE_ADAPTER = bytes32(uint256(3));
bytes32 constant SALT_UNI_VESTING = bytes32(uint256(4));

//// TOKEN JAR:
/// 1. Deploy the TokenJar
/// 3. Set the releaser on the token jar.
/// 4. Update the owner on the token jar.

/// RELEASER:
/// 2. Deploy the Releaser.
/// 5. Update the thresholdSetter on the releaser to the owner.
/// 6. Update the owner on the releaser.

/// FEE_ADAPTER
/// 7. Deploy the FeeAdapter.
/// 8. Update the feeSetter to the owner.
/// 9. Store fee tiers.
/// 10. Update the owner on the fee adapter.
/// 8. Set this contract as the feeSetter
/// 9. Set initial merkle root
/// 10. Set default fees
/// 11. Update the feeSetter to the owner.
/// 12. Store fee tiers.
/// 13. Update the owner on the fee adapter.

/// UNI_VESTING
/// 14. Deploy the UNIVesting contract.
/// 15. Update the owner on the UNIVesting contract.
constructor() {
address owner = V3_FACTORY.owner();
/// 1. Deploy the TokenJar.
TOKEN_JAR = new TokenJar{salt: SALT_TOKEN_JAR}();
/// 2. Deploy the Releaser.
RELEASER = new Firepit{salt: SALT_RELEASER}(RESOURCE, THRESHOLD, address(TOKEN_JAR));
/// 3. Set the releaser on the token jar.
TOKEN_JAR.setReleaser(address(RELEASER));
/// 4. Update the owner on the token jar.
IOwned(address(TOKEN_JAR)).transferOwnership(owner);

/// 5. Update the thresholdSetter on the releaser to the owner.
RELEASER.setThresholdSetter(owner);
/// 6. Update the owner on the releaser.
IOwned(address(RELEASER)).transferOwnership(owner);

/// 7. Deploy the FeeAdapter.
V3_FEE_ADAPTER =
new V3FeeAdapter{salt: SALT_V3_FEE_ADAPTER}(address(V3_FACTORY), address(TOKEN_JAR));

/// 8. Set this contract as the feeSetter
V3_FEE_ADAPTER.setFeeSetter(address(this));

/// 9. Set initial merkle root
V3_FEE_ADAPTER.setMerkleRoot(INITIAL_MERKLE_ROOT);

/// 10. Set default fees
V3_FEE_ADAPTER.setDefaultFeeByFeeTier(100, DEFAULT_FEE_100);
V3_FEE_ADAPTER.setDefaultFeeByFeeTier(500, DEFAULT_FEE_500);
V3_FEE_ADAPTER.setDefaultFeeByFeeTier(3000, DEFAULT_FEE_3000);
V3_FEE_ADAPTER.setDefaultFeeByFeeTier(10_000, DEFAULT_FEE_10000);

/// 11. Update the feeSetter to the owner.
V3_FEE_ADAPTER.setFeeSetter(owner);

/// 12. Store fee tiers.
V3_FEE_ADAPTER.storeFeeTier(100);
V3_FEE_ADAPTER.storeFeeTier(500);
V3_FEE_ADAPTER.storeFeeTier(3000);
V3_FEE_ADAPTER.storeFeeTier(10_000);

/// 13. Update the owner on the fee adapter.
IOwned(address(V3_FEE_ADAPTER)).transferOwnership(owner);

/// 14. Deploy the UNIVesting contract.
UNI_VESTING =
IUNIVesting(new UNIVesting{salt: SALT_UNI_VESTING}(address(RESOURCE), LABS_UNI_RECIPIENT));

/// 15. Update the owner on the UNIVesting contract.
IOwned(address(UNI_VESTING)).transferOwnership(owner);
}
}
56 changes: 56 additions & 0 deletions script/deployers/UnichainDeployer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.29;

import {V3FeeAdapter} from "../../src/feeAdapters/V3FeeAdapter.sol";
import {ITokenJar} from "../../src/interfaces/ITokenJar.sol";
import {TokenJar} from "../../src/TokenJar.sol";
import {IReleaser} from "../../src/interfaces/IReleaser.sol";
import {IV3FeeAdapter} from "../../src/interfaces/IV3FeeAdapter.sol";
import {IOwned} from "../../src/interfaces/base/IOwned.sol";
import {
OptimismBridgedResourceFirepit
} from "../../src/releasers/OptimismBridgedResourceFirepit.sol";

contract UnichainDeployer {
ITokenJar public immutable TOKEN_JAR;
IReleaser public immutable RELEASER;

// Native Bridge UNI
address public constant RESOURCE = 0x8f187aA05619a017077f5308904739877ce9eA21;
uint256 public constant THRESHOLD = 10_000e18;
// UNI Timelock alias address on Unichain
// Calculated from the aliasing scheme defined here
// https://docs.optimism.io/concepts/stack/differences#address-aliasing
// targeting 0x1a9C8182C09F50C8318d769245beA52c32BE35BC on mainnet
address public constant OWNER = 0x2BAD8182C09F50c8318d769245beA52C32Be46CD;

bytes32 constant SALT_TOKEN_JAR = bytes32(uint256(1));
bytes32 constant SALT_RELEASER = bytes32(uint256(2));

//// TOKEN JAR:
/// 1. Deploy the TokenJar
/// 3. Set the releaser on the token jar.
/// 4. Update the owner on the token jar.

/// RELEASER:
/// 2. Deploy the Releaser.
/// 5. Update the thresholdSetter on the releaser to the owner.
/// 6. Update the owner on the releaser.
constructor() {
/// 1. Deploy the TokenJar.
TOKEN_JAR = new TokenJar{salt: SALT_TOKEN_JAR}();
/// 2. Deploy the Releaser.
RELEASER = new OptimismBridgedResourceFirepit{salt: SALT_RELEASER}(
RESOURCE, THRESHOLD, address(TOKEN_JAR)
);
/// 3. Set the releaser on the token jar.
TOKEN_JAR.setReleaser(address(RELEASER));
/// 4. Update the owner on the token jar.
IOwned(address(TOKEN_JAR)).transferOwnership(OWNER);

/// 5. Update the thresholdSetter on the releaser to the owner.
RELEASER.setThresholdSetter(OWNER);
/// 6. Update the owner on the releaser.
IOwned(address(RELEASER)).transferOwnership(OWNER);
}
}
Loading
Loading