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

Mainnet spell 2025-02-21 #454

Merged
merged 17 commits into from
Feb 22, 2025
Merged
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions scripts/test-dssspell-forge.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export FOUNDRY_LIBRARIES="lib/dss-exec-lib/src/DssExecLib.sol:DssExecLib:$DSS_EX
export FOUNDRY_OPTIMIZER=false
export FOUNDRY_OPTIMIZER_RUNS=200
export FOUNDRY_ROOT_CHAINID=1
export FOUNDRY_GAS_LIMIT=18446744073709551615
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NOTE: I've increased gas limit for the tests due to the error arising from testArbitrumTokenGatewayIntegration function. More specifically, the error EvmError: MemoryOOG is thrown by arbitrumDomain.relayFromHost(false) line in the test, when this env variable is not present. Chosen FOUNDRY_GAS_LIMIT value is arbitrary and is simply twice bigger than the default value specified in the foundry docs. This adjustment should not affect execution of the spell on the mainnet.


TEST_ARGS=''

Expand Down
337 changes: 313 additions & 24 deletions src/DssSpell.sol

Large diffs are not rendered by default.

149 changes: 144 additions & 5 deletions src/DssSpell.t.base.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {stdStorage, StdStorage} from "forge-std/Test.sol";
import "./test/rates.sol";
import "./test/addresses_mainnet.sol";
import "./test/addresses_base.sol";
import "./test/addresses_arbitrum.sol";
import "./test/addresses_deployers.sol";
import "./test/addresses_wallets.sol";
import "./test/config.sol";
Expand Down Expand Up @@ -371,14 +372,67 @@ interface L2TokenBridgeLike {
) external;
}

interface L1TokenGatewayLike {
function counterpartGateway() external view returns (address);
function escrow() external view returns (address);
function getImplementation() external view returns (address);
function inbox() external view returns (address);
function isOpen() external view returns (uint256);
function l1Router() external view returns (address);
function l1ToL2Token(address) external view returns (address);
function outboundTransfer(
address l1Token,
address to,
uint256 amount,
uint256 maxGas,
uint256 gasPriceBid,
bytes memory data
) external payable returns (bytes memory res);
function outboundTransferCustomRefund(
address l1Token,
address refundTo,
address to,
uint256 amount,
uint256 maxGas,
uint256 gasPriceBid,
bytes calldata data
) external payable returns (bytes memory res);
function version() external view returns (string memory);
}

interface L2TokenGatewayLike {
function counterpartGateway() external view returns (address);
function getImplementation() external view returns (address);
function isOpen() external view returns (uint256);
function maxWithdraws(address) external view returns (uint256);
function l2Router() external view returns (address);
function l1ToL2Token(address) external view returns (address);
function outboundTransfer(
address l1Token,
address to,
uint256 amount,
bytes memory data
) external payable returns (bytes memory res);
function outboundTransfer(
address l1Token,
address to,
uint256 amount,
uint256 maxGas,
uint256 gasPriceBid,
bytes memory data
) external payable returns (bytes memory res);
function version() external view returns (string memory);
}

contract DssSpellTestBase is Config, DssTest {
using stdStorage for StdStorage;

Rates rates = new Rates();
Addresses addr = new Addresses();
BaseAddresses base = new BaseAddresses();
Deployers deployers = new Deployers();
Wallets wallets = new Wallets();
Rates rates = new Rates();
Addresses addr = new Addresses();
BaseAddresses base = new BaseAddresses();
ArbitrumAddresses arbitrum = new ArbitrumAddresses();
Deployers deployers = new Deployers();
Wallets wallets = new Wallets();

// ADDRESSES
ChainlogAbstract chainLog = ChainlogAbstract( addr.addr("CHANGELOG"));
Expand Down Expand Up @@ -2133,6 +2187,91 @@ contract DssSpellTestBase is Config, DssTest {
}
}

function _testArbitrumTokenGatewayIntegration(
L1TokenGatewayLike l1Gateway,
L2TokenGatewayLike l2Gateway,
address l1Escrow,
address[] memory l1Tokens,
address[] memory l2Tokens,
uint256[] memory maxWithdrawals
) public {
for (uint i = 0; i < l1Tokens.length; i ++) {
rootDomain.selectFork();

// test L1 side of gateway init
assertEq(GemAbstract(l1Tokens[i]).allowance(l1Escrow, address(l1Gateway)), maxWithdrawals[i]);
assertEq(l1Gateway.l1ToL2Token(l1Tokens[i]), l2Tokens[i]);

arbitrumDomain.selectFork();
// test L2 side of gateway init
assertEq(l2Gateway.l1ToL2Token(l1Tokens[i]), l2Tokens[i]);
assertEq(l2Gateway.maxWithdraws(l2Tokens[i]), maxWithdrawals[i]);

// ------- Test Deposit -------

rootDomain.selectFork();
uint256 maxSubmissionCost = 0.1 ether;
uint256 maxGas = 1_000_000;
uint256 gasPriceBid = 1 gwei;
uint256 value = maxSubmissionCost + maxGas * gasPriceBid;

deal(l1Tokens[i], address(this), 100 ether);
assertEq(GemAbstract(l1Tokens[i]).balanceOf(address(this)), 100 ether);

GemAbstract(l1Tokens[i]).approve(address(l1Gateway), 100 ether);
uint256 escrowBeforeBalance = GemAbstract(l1Tokens[i]).balanceOf(l1Escrow);

l1Gateway.outboundTransferCustomRefund{value: value}(
address(l1Tokens[i]),
address(0x7ef),
address(0xb0b),
50 ether,
maxGas,
gasPriceBid,
abi.encode(maxSubmissionCost, "")
);
l1Gateway.outboundTransfer{value: value}(
address(l1Tokens[i]),
address(0xb0b),
50 ether,
maxGas,
gasPriceBid,
abi.encode(maxSubmissionCost, "")
);

assertEq(GemAbstract(l1Tokens[i]).balanceOf(l1Escrow), escrowBeforeBalance + 100 ether);

arbitrumDomain.relayFromHost(true);
assertEq(GemAbstract(l2Tokens[i]).balanceOf(address(0xb0b)), 100 ether);

// ------- Test Widthrawl -------

vm.startPrank(address(0xb0b));
GemAbstract(l2Tokens[i]).approve(address(l2Gateway), 100 ether);
l2Gateway.outboundTransfer(
l1Tokens[i],
address(0xced),
50 ether,
0,
0,
""
);
l2Gateway.outboundTransfer(
l1Tokens[i],
address(0xced),
50 ether,
""
);
vm.stopPrank();

assertEq(GemAbstract(l2Tokens[i]).balanceOf(address(0xb0b)), 0);

arbitrumDomain.relayToHost(true);

assertEq(GemAbstract(l1Tokens[i]).balanceOf(address(0xced)), 100 ether);
}
}

function _to18ConversionFactor(LitePsmLike litePsm) internal view returns (uint256) {
return litePsm.to18ConversionFactor();
}
Expand Down
99 changes: 89 additions & 10 deletions src/DssSpell.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ interface SequencerLike {
function hasJob(address job) external view returns (bool);
}

interface L2TokenGatewaySpellLike {
function l2Gateway() external view returns (address);
}

contract DssSpellTest is DssSpellTestBase {
// DO NOT TOUCH THE FOLLOWING TESTS, THEY SHOULD BE RUN ON EVERY SPELL
function testGeneral() public {
Expand Down Expand Up @@ -267,7 +271,7 @@ contract DssSpellTest is DssSpellTestBase {
);
}

function testLockstakeIlkIntegration() public { // add the `skipped` modifier to skip
function testLockstakeIlkIntegration() public skipped { // add the `skipped` modifier to skip
_vote(address(spell));
_scheduleWaitAndCast(address(spell));
assertTrue(spell.done(), "TestError/spell-not-done");
Expand Down Expand Up @@ -700,7 +704,7 @@ contract DssSpellTest is DssSpellTestBase {
int256 sky;
}

function testPayments() public skipped { // add the `skipped` modifier to skip
function testPayments() public { // add the `skipped` modifier to skip
// Note: set to true when there are additional DAI/USDS operations (e.g. surplus buffer sweeps, SubDAO draw-downs) besides direct transfers
bool ignoreTotalSupplyDaiUsds = true;

Expand All @@ -709,23 +713,30 @@ contract DssSpellTest is DssSpellTestBase {
// the destination address,
// the amount to be paid
// Initialize the array with the number of payees
Payee[2] memory payees = [
Payee[9] memory payees = [
Payee(address(usds), wallets.addr("BLUE"), 87_601 ether), // Note: ether is only a keyword helper
Payee(address(usds), wallets.addr("BONAPUBLICA"), 4_000 ether), // Note: ether is only a keyword helper
Payee(address(usds), wallets.addr("BYTERON"), 4_000 ether), // Note: ether is only a keyword helper
Payee(address(usds), wallets.addr("CLOAKY_2"), 22_835 ether), // Note: ether is only a keyword helper
Payee(address(usds), wallets.addr("JULIACHANG"), 4_000 ether), // Note: ether is only a keyword helper
Payee(address(usds), wallets.addr("PBG"), 387 ether), // Note: ether is only a keyword helper
Payee(address(usds), wallets.addr("INTEGRATION_BOOST_INITIATIVE"), 3_000_000 ether), // Note: ether is only a keyword helper
Payee(address(usds), wallets.addr("GFXLABS"), 1_000 ether) // Note: ether is only a keyword helper
Payee(address(sky), wallets.addr("BLUE"), 550_000 ether), // Note: ether is only a keyword helper
Payee(address(sky), wallets.addr("CLOAKY_2"), 438_000 ether) // Note: ether is only a keyword helper
];

// Fill the total values from exec sheet
PaymentAmounts memory expectedTotalPayments = PaymentAmounts({
dai: 0 ether, // Note: ether is only a keyword helper
mkr: 0 ether, // Note: ether is only a keyword helper
usds: 3_001_000 ether, // Note: ether is only a keyword helper
sky: 0 ether // Note: ether is only a keyword helper
usds: 3_122_823 ether, // Note: ether is only a keyword helper
sky: 988_000 ether // Note: ether is only a keyword helper
});

// Fill the total values based on the source for the transfers above
TreasuryAmounts memory expectedTreasuryBalancesDiff = TreasuryAmounts({
mkr: 0,
sky: 0
mkr: -41_166666666666666667,
sky: 8000
});

// Vote, schedule and warp, but not yet cast (to get correct surplus balance)
Expand Down Expand Up @@ -1095,9 +1106,9 @@ contract DssSpellTest is DssSpellTestBase {
}

// SPARK TESTS
function testSparkSpellIsExecuted() public skipped { // add the `skipped` modifier to skip
function testSparkSpellIsExecuted() public { // add the `skipped` modifier to skip
address SPARK_PROXY = addr.addr('SPARK_PROXY');
address SPARK_SPELL = address(0xD5c59b7c1DD8D2663b4c826574ed968B2C8329C0); // Insert Spark spell address
address SPARK_SPELL = address(0x9EAa8d72BD731BE8eD71D768a912F6832492071e); // Insert Spark spell address

vm.expectCall(
SPARK_PROXY,
Expand All @@ -1114,5 +1125,73 @@ contract DssSpellTest is DssSpellTestBase {
}

// SPELL-SPECIFIC TESTS GO BELOW
L1TokenGatewayLike immutable ARBITRUM_TOKEN_BRIDGE = L1TokenGatewayLike(addr.addr("ARBITRUM_TOKEN_BRIDGE"));
address immutable ARBITRUM_TOKEN_BRIDGE_IMP = addr.addr("ARBITRUM_TOKEN_BRIDGE_IMP");
address immutable ARBITRUM_ROUTER = addr.addr("ARBITRUM_ROUTER");
address immutable USDS = addr.addr("USDS");
address immutable SUSDS = addr.addr("SUSDS");
address immutable ARBITRUM_ESCROW = addr.addr("ARBITRUM_ESCROW");
address immutable ARBITRUM_INBOX = addr.addr("ARBITRUM_INBOX");
L2TokenGatewayLike immutable L2_ARBITRUM_TOKEN_BRIDGE = L2TokenGatewayLike(arbitrum.addr("L2_TOKEN_BRIDGE"));
address immutable L2_ARBITRUM_TOKEN_BRIDGE_IMP = arbitrum.addr("L2_TOKEN_BRIDGE_IMP");
address immutable L2_ARBITRUM_ROUTER = arbitrum.addr("L2_ROUTER");
address immutable L2_USDS = arbitrum.addr("L2_USDS");
address immutable L2_SUSDS = arbitrum.addr("L2_SUSDS");
L2TokenGatewaySpellLike immutable L2_ARBITRUM_TOKEN_BRIDGE_SPELL = L2TokenGatewaySpellLike(arbitrum.addr("L2_TOKEN_BRIDGE_SPELL"));

function testArbitrumTokenGatewayIntegration() public {
_setupRootDomain();
rootDomain.selectFork();
arbitrumDomain = new ArbitrumDomain(config, getRelativeChain("arbitrum_one"), rootDomain);

// ------ Sanity checks -------
rootDomain.selectFork();

require(ARBITRUM_TOKEN_BRIDGE.isOpen() == 1, "ArbitrumTokenBridge/not-open");
require(ARBITRUM_TOKEN_BRIDGE.l1Router() == ARBITRUM_ROUTER, "ArbitrumTokenBridge/l1-rounter-mismatch");
require(ARBITRUM_TOKEN_BRIDGE.inbox() == ARBITRUM_INBOX, "ArbitrumTokenBridge/inbox-mismatchpen");
require(ARBITRUM_TOKEN_BRIDGE.counterpartGateway() == address(L2_ARBITRUM_TOKEN_BRIDGE), "ArbitrumTokenBridge/counterpart-gateway-mismatch");
require(ARBITRUM_TOKEN_BRIDGE.getImplementation() == ARBITRUM_TOKEN_BRIDGE_IMP, "ArbitrumTokenBridge/imp-does-not-match");
require(keccak256(bytes(ARBITRUM_TOKEN_BRIDGE.version())) == keccak256("1"), "ArbitrumTokenBridge/version-does-not-match");

arbitrumDomain.selectFork();

require(L2_ARBITRUM_TOKEN_BRIDGE.isOpen() == 1, "L2ArbitrumTokenBridge/not-open");
require(L2_ARBITRUM_TOKEN_BRIDGE.l2Router() == L2_ARBITRUM_ROUTER, "L2ArbitrumTokenBridge/l2-rounter-mismatch");
require(L2_ARBITRUM_TOKEN_BRIDGE.counterpartGateway() == address(ARBITRUM_TOKEN_BRIDGE), "L2ArbitrumTokenBridge/counterpart-gateway-mismatch");
require(L2_ARBITRUM_TOKEN_BRIDGE.getImplementation() == L2_ARBITRUM_TOKEN_BRIDGE_IMP, "L2ArbitrumTokenBridge/imp-does-not-match");
require(keccak256(bytes(L2_ARBITRUM_TOKEN_BRIDGE.version())) == keccak256("1"), "L2ArbitrumTokenBridge/version-does-not-match");

require(L2_ARBITRUM_TOKEN_BRIDGE_SPELL.l2Gateway() == address(L2_ARBITRUM_TOKEN_BRIDGE), "L2ArbitrumTokenBridgeSpell/l2-gateway-mismatch");

rootDomain.selectFork();
_vote(address(spell));
_scheduleWaitAndCast(address(spell));
assertTrue(spell.done(), "TestError/spell-not-done");
// Read all x-chain messages
arbitrumDomain.relayFromHost(false);

require(ARBITRUM_TOKEN_BRIDGE.escrow() == ARBITRUM_ESCROW, "ArbitrumTokenBridge/escrow-does-not-match");

address[] memory l1Tokens = new address[](2);
l1Tokens[0] = USDS;
l1Tokens[1] = SUSDS;

address[] memory l2Tokens = new address[](2);
l2Tokens[0] = L2_USDS;
l2Tokens[1] = L2_SUSDS;

uint256[] memory maxWithdrawals = new uint256[](2);
maxWithdrawals[0] = type(uint256).max;
maxWithdrawals[1] = type(uint256).max;

_testArbitrumTokenGatewayIntegration(
ARBITRUM_TOKEN_BRIDGE,
L2_ARBITRUM_TOKEN_BRIDGE,
ARBITRUM_ESCROW,
l1Tokens,
l2Tokens,
maxWithdrawals
);
}
}
22 changes: 22 additions & 0 deletions src/dependencies/arbitrum-token-bridge/L1TokenGatewayInstance.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-FileCopyrightText: © 2024 Dai Foundation <www.daifoundation.org>
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

pragma solidity >=0.8.0;

struct L1TokenGatewayInstance {
address gateway;
address gatewayImp;
}
23 changes: 23 additions & 0 deletions src/dependencies/arbitrum-token-bridge/L2TokenGatewayInstance.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: © 2024 Dai Foundation <www.daifoundation.org>
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

pragma solidity >=0.8.0;

struct L2TokenGatewayInstance {
address gateway;
address gatewayImp;
address spell;
}
Loading