diff --git a/lib/angle-transmuter b/lib/angle-transmuter index 41d56b5..1c417c4 160000 --- a/lib/angle-transmuter +++ b/lib/angle-transmuter @@ -1 +1 @@ -Subproject commit 41d56b5fad0fc1582249c22e14a7206a84fa471c +Subproject commit 1c417c47b0cecdfc07fd8175b71c20c3524ac593 diff --git a/scripts/foundry/transmuter/TransmuterAddCollateralEURCV.s.sol b/scripts/foundry/transmuter/TransmuterAddCollateralEURCV.s.sol new file mode 100644 index 0000000..2bfc45a --- /dev/null +++ b/scripts/foundry/transmuter/TransmuterAddCollateralEURCV.s.sol @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.19; + +import { console } from "forge-std/console.sol"; +import "transmuter/transmuter/Storage.sol" as Storage; +import { ITransmuter, ISettersGovernor, ISettersGuardian, ISwapper } from "transmuter/interfaces/ITransmuter.sol"; +import { Enum } from "safe/Safe.sol"; +import { MultiSend, Utils } from "../Utils.s.sol"; +import "../Constants.s.sol"; + +contract TransmuterAddCollateralEURCV is Utils { + address public constant COLLATERAL_TO_ADD = 0x5F7827FDeb7c20b443265Fc2F40845B715385Ff2; + + bytes oracleConfigCollatToAdd; + uint64[] public xFeeMint; + int64[] public yFeeMint; + uint64[] public xFeeBurn; + int64[] public yFeeBurn; + address public agToken; + + function run() external { + uint256 chainId = vm.envUint("CHAIN_ID"); + + address safe = _chainToContract(chainId, ContractType.GovernorMultisig); + ITransmuter transmuter = ITransmuter(_chainToContract(chainId, ContractType.TransmuterAgEUR)); + agToken = address(transmuter.agToken()); + bytes memory transactions; + + uint64[] memory xFeeBurn = new uint64[](1); + uint64[] memory xFeeMint = new uint64[](3); + int64[] memory yFeeMint = new int64[](xFeeMint.length); + int64[] memory yFeeBurn = new int64[](xFeeBurn.length); + xFeeBurn[0] = 1e9; + yFeeBurn[0] = 0.0005e9; + + xFeeMint[0] = 0; + xFeeMint[1] = 0.29e9; + xFeeMint[2] = 0.30e9; + yFeeMint[0] = 0; + yFeeMint[1] = 0; + yFeeMint[2] = 100e9 - 1; + + // Add the new collateral + { + address to = address(transmuter); + uint8 isDelegateCall = 0; + { + bytes memory readData; + bytes memory targetData; + oracleConfigCollatToAdd = abi.encode( + Storage.OracleReadType.NO_ORACLE, + Storage.OracleReadType.STABLE, + readData, + targetData, + // With no oracle the below oracles are useless + abi.encode(uint128(0), uint128(0)) + ); + } + { + bytes memory data = abi.encodeWithSelector(ISettersGovernor.addCollateral.selector, COLLATERAL_TO_ADD); + uint256 dataLength = data.length; + bytes memory internalTx = abi.encodePacked(isDelegateCall, to, uint256(0), dataLength, data); + transactions = abi.encodePacked(transactions, internalTx); + } + { + // Mint fees + bytes memory data = abi.encodeWithSelector( + ISettersGuardian.setFees.selector, + COLLATERAL_TO_ADD, + xFeeMint, + yFeeMint, + true + ); + uint256 dataLength = data.length; + bytes memory internalTx = abi.encodePacked(isDelegateCall, to, uint256(0), dataLength, data); + transactions = abi.encodePacked(transactions, internalTx); + } + { + // Burn fees + bytes memory data = abi.encodeWithSelector( + ISettersGuardian.setFees.selector, + COLLATERAL_TO_ADD, + xFeeBurn, + yFeeBurn, + false + ); + uint256 dataLength = data.length; + bytes memory internalTx = abi.encodePacked(isDelegateCall, to, uint256(0), dataLength, data); + transactions = abi.encodePacked(transactions, internalTx); + } + { + bytes memory data = abi.encodeWithSelector( + ISettersGovernor.setOracle.selector, + COLLATERAL_TO_ADD, + oracleConfigCollatToAdd + ); + uint256 dataLength = data.length; + bytes memory internalTx = abi.encodePacked(isDelegateCall, to, uint256(0), dataLength, data); + transactions = abi.encodePacked(transactions, internalTx); + } + { + bytes memory data = abi.encodeWithSelector( + ISettersGuardian.togglePause.selector, + COLLATERAL_TO_ADD, + Storage.ActionType.Mint + ); + uint256 dataLength = data.length; + bytes memory internalTx = abi.encodePacked(isDelegateCall, to, uint256(0), dataLength, data); + transactions = abi.encodePacked(transactions, internalTx); + } + { + bytes memory data = abi.encodeWithSelector( + ISettersGuardian.togglePause.selector, + COLLATERAL_TO_ADD, + Storage.ActionType.Burn + ); + uint256 dataLength = data.length; + bytes memory internalTx = abi.encodePacked(isDelegateCall, to, uint256(0), dataLength, data); + transactions = abi.encodePacked(transactions, internalTx); + } + } + + bytes memory payloadMultiSend = abi.encodeWithSelector(MultiSend.multiSend.selector, transactions); + _serializeJson(chainId, address(_chainToMultiSend(chainId)), uint256(0), payloadMultiSend, Enum.Operation.DelegateCall, hex"", safe); + } +} diff --git a/scripts/foundry/transmuter/TransmuterAddCollateralXEVT.s..sol b/scripts/foundry/transmuter/TransmuterAddCollateralXEVT.s..sol new file mode 100644 index 0000000..7ab647f --- /dev/null +++ b/scripts/foundry/transmuter/TransmuterAddCollateralXEVT.s..sol @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.19; + +import { console } from "forge-std/console.sol"; +import "transmuter/transmuter/Storage.sol" as Storage; +import { ITransmuter, ISettersGovernor, ISettersGuardian, ISwapper } from "transmuter/interfaces/ITransmuter.sol"; +import { Enum } from "safe/Safe.sol"; +import { MultiSend, Utils } from "../Utils.s.sol"; +import "../Constants.s.sol"; + +contract TransmuterAddCollateralXEVT is Utils { + address public constant COLLATERAL_TO_ADD = 0x3Ee320c9F73a84D1717557af00695A34b26d1F1d; + + bytes oracleConfigCollatToAdd; + uint64[] public xFeeMint; + int64[] public yFeeMint; + uint64[] public xFeeBurn; + int64[] public yFeeBurn; + address public agToken; + + function run() external { + uint256 chainId = vm.envUint("CHAIN_ID"); + + ITransmuter transmuter = ITransmuter(_chainToContract(chainId, ContractType.TransmuterAgEUR)); + agToken = address(transmuter.agToken()); + bytes memory transactions; + uint8 isDelegateCall = 0; + address to = address(transmuter); + uint256 value = 0; + + uint64[] memory xFeeBurn = new uint64[](3); + uint64[] memory xFeeMint = new uint64[](3); + int64[] memory yFeeMint = new int64[](xFeeMint.length); + int64[] memory yFeeBurn = new int64[](xFeeBurn.length); + xFeeBurn[0] = 1e9; + xFeeBurn[1] = 0.06e9; + xFeeBurn[2] = 0.05e9; + yFeeBurn[0] = 0.005e9; + yFeeBurn[1] = 0.005e9; + yFeeBurn[2] = 0.999e9; + + xFeeMint[0] = 0; + xFeeMint[1] = 0.19e9; + xFeeMint[2] = 0.20e9; + yFeeMint[0] = 0; + yFeeMint[1] = 0; + yFeeMint[2] = 100e9 - 1; + + // Add the new collateral + { + { + address oracle = 0x6B102047A4bB943DE39233E44487F2d57bDCb33e; + uint256 normalizationFactor = 1e18; // price == 36 decimals + bytes memory readData; + bytes memory targetData = abi.encode(oracle, normalizationFactor); + oracleConfigCollatToAdd = abi.encode( + Storage.OracleReadType.NO_ORACLE, + Storage.OracleReadType.MORPHO_ORACLE, + readData, + targetData, + abi.encode(uint128(0), uint128(0)) + ); + } + { + bytes memory data = abi.encodeWithSelector(ISettersGovernor.addCollateral.selector, COLLATERAL_TO_ADD); + uint256 dataLength = data.length; + bytes memory internalTx = abi.encodePacked(isDelegateCall, to, value, dataLength, data); + transactions = abi.encodePacked(transactions, internalTx); + } + { + // Mint fees + bytes memory data = abi.encodeWithSelector( + ISettersGuardian.setFees.selector, + COLLATERAL_TO_ADD, + xFeeMint, + yFeeMint, + true + ); + uint256 dataLength = data.length; + bytes memory internalTx = abi.encodePacked(isDelegateCall, to, value, dataLength, data); + transactions = abi.encodePacked(transactions, internalTx); + } + { + // Burn fees + bytes memory data = abi.encodeWithSelector( + ISettersGuardian.setFees.selector, + COLLATERAL_TO_ADD, + xFeeBurn, + yFeeBurn, + false + ); + uint256 dataLength = data.length; + bytes memory internalTx = abi.encodePacked(isDelegateCall, to, value, dataLength, data); + transactions = abi.encodePacked(transactions, internalTx); + } + { + bytes memory data = abi.encodeWithSelector( + ISettersGovernor.setOracle.selector, + COLLATERAL_TO_ADD, + oracleConfigCollatToAdd + ); + uint256 dataLength = data.length; + bytes memory internalTx = abi.encodePacked(isDelegateCall, to, value, dataLength, data); + transactions = abi.encodePacked(transactions, internalTx); + } + { + bytes memory data = abi.encodeWithSelector( + ISettersGuardian.togglePause.selector, + COLLATERAL_TO_ADD, + Storage.ActionType.Mint + ); + uint256 dataLength = data.length; + bytes memory internalTx = abi.encodePacked(isDelegateCall, to, value, dataLength, data); + transactions = abi.encodePacked(transactions, internalTx); + } + { + bytes memory data = abi.encodeWithSelector( + ISettersGuardian.togglePause.selector, + COLLATERAL_TO_ADD, + Storage.ActionType.Burn + ); + uint256 dataLength = data.length; + bytes memory internalTx = abi.encodePacked(isDelegateCall, to, value, dataLength, data); + transactions = abi.encodePacked(transactions, internalTx); + } + } + + bytes memory payloadMultiSend = abi.encodeWithSelector(MultiSend.multiSend.selector, transactions); + address multiSend = address(_chainToMultiSend(chainId)); + _serializeJson(chainId, multiSend, 0, payloadMultiSend, Enum.Operation.DelegateCall, hex"", _chainToContract(chainId, ContractType.GovernorMultisig)); + } +} diff --git a/test/transmuter/TransmuterAddCollateralEURCV.t.sol b/test/transmuter/TransmuterAddCollateralEURCV.t.sol new file mode 100644 index 0000000..d7ea078 --- /dev/null +++ b/test/transmuter/TransmuterAddCollateralEURCV.t.sol @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.19; + +import { stdJson } from "forge-std/StdJson.sol"; +import { console } from "forge-std/console.sol"; +import { MockSafe } from "../mock/MockSafe.sol"; +import { BaseTest } from "../BaseTest.t.sol"; +import "../../scripts/foundry/Constants.s.sol"; +import "transmuter/transmuter/Storage.sol" as Storage; +import "transmuter/utils/Errors.sol" as Errors; +import { ITransmuter, ISettersGovernor, ISettersGuardian, ISwapper, IGetters } from "transmuter/interfaces/ITransmuter.sol"; +import { MAX_MINT_FEE, MAX_BURN_FEE } from "transmuter/utils/Constants.sol"; +import { IERC20 } from "oz/token/ERC20/IERC20.sol"; + +contract TransmuterAddCollateralEURCVTest is BaseTest { + using stdJson for string; + + ITransmuter public transmuter; + IAgToken public agToken; + address[] public collateralList; + + function setUp() public override { + super.setUp(); + } + + function testScript() external { + uint256 chainId = json.readUint("$.chainId"); + vm.selectFork(forkIdentifier[chainId]); + + // TODO + StablecoinType fiat = StablecoinType.EUR; + // TODO END + + address gnosisSafe = _chainToContract(chainId, ContractType.GovernorMultisig); + transmuter = ITransmuter(address(_getTransmuter(chainId, fiat))); + agToken = _getAgToken(chainId, fiat); + + address to = json.readAddress("$.to"); + // uint256 value = json.readUint("$.value"); + uint256 operation = json.readUint("$.operation"); + bytes memory payload = json.readBytes("$.data"); + + // Verify that the call will succeed + MockSafe mockSafe = new MockSafe(); + vm.etch(gnosisSafe, address(mockSafe).code); + vm.prank(gnosisSafe); + (bool success, ) = gnosisSafe.call(abi.encode(address(to), payload, operation, 1e7)); + if (!success) revert(); + + collateralList = transmuter.getCollateralList(); + assertEq(agToken.isMinter(address(transmuter)), true); + assertEq(collateralList.length, 4); + assertEq(collateralList[3], address(0x5F7827FDeb7c20b443265Fc2F40845B715385Ff2)); + + // Check parameters are correct for the new collateral + uint256 newCollateralIndex = 3; + address newCollateral = collateralList[newCollateralIndex]; + { + ( + Storage.OracleReadType oracleType, + Storage.OracleReadType targetType, + bytes memory oracleData, + bytes memory targetData, + bytes memory hyperparameters + ) = transmuter.getOracle(newCollateral); + assertEq(uint256(oracleType), uint256(Storage.OracleReadType.NO_ORACLE)); + assertEq(uint256(targetType), uint256(Storage.OracleReadType.STABLE)); + assertEq(oracleData.length, 0); + assertEq(targetData.length, 0); + assertEq(hyperparameters, abi.encode(uint128(0), uint128(0))); + } + + { + (uint64[] memory xFeeMint, int64[] memory yFeeMint) = transmuter.getCollateralMintFees(newCollateral); + (uint64[] memory xFeeBurn, int64[] memory yFeeBurn) = transmuter.getCollateralBurnFees(newCollateral); + + assertEq(xFeeMint.length, 3); + assertEq(yFeeMint.length, 3); + assertEq(xFeeBurn.length, 1); + assertEq(yFeeBurn.length, 1); + + assertEq(xFeeMint[0], 0); + assertEq(xFeeMint[1], 0.29e9); + assertEq(xFeeMint[2], 0.30e9); + assertEq(xFeeBurn[0], 1e9); + + assertEq(yFeeBurn[0], 0.0005e9); + assertEq(yFeeMint[0], 0); + assertEq(yFeeMint[1], 0); + assertEq(yFeeMint[2], 100e9 - 1); + } + + // Check storage new collat + { + Storage.Collateral memory collatInfo = transmuter.getCollateralInfo(newCollateral); + assertEq(collatInfo.isManaged, 0); + assertEq(collatInfo.isMintLive, 1); + assertEq(collatInfo.isBurnLive, 1); + assertEq(collatInfo.decimals, 18); + assertEq(collatInfo.onlyWhitelisted, 0); + assertEq(collatInfo.normalizedStables, 0); + assertEq(collatInfo.managerData.subCollaterals.length, 0); + assertEq(collatInfo.managerData.config.length, 0); + } + + // Test oracle values returned + { + (uint256 mint, uint256 burn, uint256 ratio, uint256 minRatio, uint256 redemption) = transmuter + .getOracleValues(newCollateral); + assertEq(mint, BASE_18); + assertEq(burn, BASE_18); + assertEq(ratio, BASE_18); + assertEq(redemption, BASE_18); + } + + // Check quotes are working on the added collateral + { + // we ca do some quoteIn and quoteOut + assertEq(BASE_18, transmuter.quoteOut(BASE_18, newCollateral, address(agToken))); + assertEq(BASE_18, transmuter.quoteIn(BASE_18, newCollateral, address(agToken))); + } + + transmuter.quoteRedemptionCurve(BASE_18); + } +} diff --git a/test/transmuter/TransmuterAddCollateralXEVT.t.sol b/test/transmuter/TransmuterAddCollateralXEVT.t.sol new file mode 100644 index 0000000..8d16df5 --- /dev/null +++ b/test/transmuter/TransmuterAddCollateralXEVT.t.sol @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.19; + +import { stdJson } from "forge-std/StdJson.sol"; +import { console } from "forge-std/console.sol"; +import { MockSafe } from "../mock/MockSafe.sol"; +import { BaseTest } from "../BaseTest.t.sol"; +import "../../scripts/foundry/Constants.s.sol"; +import "transmuter/transmuter/Storage.sol" as Storage; +import "transmuter/utils/Errors.sol" as Errors; +import { ITransmuter, ISettersGovernor, ISettersGuardian, ISwapper, IGetters } from "transmuter/interfaces/ITransmuter.sol"; +import { MAX_MINT_FEE, MAX_BURN_FEE } from "transmuter/utils/Constants.sol"; +import { IERC20 } from "oz/token/ERC20/IERC20.sol"; +import { IERC4626 } from "oz/token/ERC20/extensions/ERC4626.sol"; + +contract TransmuterAddCollateralXEVTTest is BaseTest { + using stdJson for string; + + ITransmuter public transmuter; + IAgToken public agToken; + address[] public collateralList; + + function setUp() public override { + super.setUp(); + } + + function testScript() external { + uint256 chainId = json.readUint("$.chainId"); + vm.selectFork(forkIdentifier[chainId]); + + // TODO + StablecoinType fiat = StablecoinType.EUR; + // TODO END + + address gnosisSafe = _chainToContract(chainId, ContractType.GovernorMultisig); + transmuter = ITransmuter(address(_getTransmuter(chainId, fiat))); + agToken = _getAgToken(chainId, fiat); + + address to = json.readAddress("$.to"); + // uint256 value = json.readUint("$.value"); + uint256 operation = json.readUint("$.operation"); + bytes memory payload = json.readBytes("$.data"); + + // Verify that the call will succeed + MockSafe mockSafe = new MockSafe(); + vm.etch(gnosisSafe, address(mockSafe).code); + vm.prank(gnosisSafe); + (bool success, ) = gnosisSafe.call(abi.encode(address(to), payload, operation, 1e7)); + if (!success) revert(); + + collateralList = transmuter.getCollateralList(); + assertEq(agToken.isMinter(address(transmuter)), true); + assertEq(collateralList.length, 4); + assertEq(collateralList[3], address(0x3Ee320c9F73a84D1717557af00695A34b26d1F1d)); + + // Check parameters are correct for the new collateral + uint256 newCollateralIndex = 3; + address newCollateral = collateralList[newCollateralIndex]; + { + ( + Storage.OracleReadType oracleType, + Storage.OracleReadType targetType, + bytes memory oracleData, + bytes memory targetData, + bytes memory hyperparameters + ) = transmuter.getOracle(newCollateral); + assertEq(uint256(oracleType), uint256(Storage.OracleReadType.NO_ORACLE)); + assertEq(uint256(targetType), uint256(Storage.OracleReadType.MORPHO_ORACLE)); + assertEq(oracleData.length, 0); + assertNotEq(targetData.length, 0); + assertEq(hyperparameters, abi.encode(uint128(0), uint128(0))); + } + + { + (uint64[] memory xFeeMint, int64[] memory yFeeMint) = transmuter.getCollateralMintFees(newCollateral); + (uint64[] memory xFeeBurn, int64[] memory yFeeBurn) = transmuter.getCollateralBurnFees(newCollateral); + + assertEq(xFeeMint.length, 3); + assertEq(yFeeMint.length, 3); + assertEq(xFeeBurn.length, 3); + assertEq(yFeeBurn.length, 3); + + assertEq(xFeeMint[0], 0); + assertEq(xFeeMint[1], 0.19e9); + assertEq(xFeeMint[2], 0.20e9); + assertEq(xFeeBurn[0], 1e9); + assertEq(xFeeBurn[1], 0.06e9); + assertEq(xFeeBurn[2], 0.05e9); + + assertEq(yFeeBurn[0], 0.005e9); + assertEq(yFeeBurn[1], 0.005e9); + assertEq(yFeeBurn[2], 0.999e9); + assertEq(yFeeMint[0], 0); + assertEq(yFeeMint[1], 0); + assertEq(yFeeMint[2], 100e9 - 1); + } + + // Check storage new collat + { + Storage.Collateral memory collatInfo = transmuter.getCollateralInfo(newCollateral); + assertEq(collatInfo.isManaged, 0); + assertEq(collatInfo.isMintLive, 1); + assertEq(collatInfo.isBurnLive, 1); + assertEq(collatInfo.decimals, 6); + assertEq(collatInfo.onlyWhitelisted, 0); + assertEq(collatInfo.normalizedStables, 0); + assertEq(collatInfo.managerData.subCollaterals.length, 0); + assertEq(collatInfo.managerData.config.length, 0); + } + + // Test oracle values returned + uint256 value = IERC4626(newCollateral).convertToAssets(1e6) * 1e12; + { + (uint256 mint, uint256 burn, uint256 ratio, uint256 minRatio, uint256 redemption) = transmuter + .getOracleValues(newCollateral); + assertEq(mint, value); + assertEq(burn, value); + assertEq(ratio, BASE_18); + assertEq(redemption, value); + } + + // Check quotes are working on the added collateral + { + // we ca do some quoteIn and quoteOut + transmuter.quoteOut(value, newCollateral, address(agToken)); + transmuter.quoteIn(value, newCollateral, address(agToken)); + } + + transmuter.quoteRedemptionCurve(BASE_18); + } +}