From 3ea9f59d4de1b1812ca50d960ff6afc0e21d0573 Mon Sep 17 00:00:00 2001 From: gs8nrv <55771972+GuillaumeNervoXS@users.noreply.github.com> Date: Tue, 5 Nov 2024 13:08:52 +0100 Subject: [PATCH] feat: WIP deploy EUR transmuter on Base (#118) * feat: WIP deploy EUR transmuter on Base * tests done * feat: deployed --- .../configs/ProductionSidechain.sol | 52 +++--- foundry.toml | 15 +- lib/forge-std | 2 +- lib/utils | 2 +- package.json | 4 +- scripts/DeployTransmuterSidechain.s.sol | 18 +- scripts/Helpers.s.sol | 128 ++++++++----- scripts/interaction/transmuter/Swap.s.sol | 2 +- .../utils/TransmuterDeploymentHelper.s.sol | 2 +- test/scripts/DeployTransmuterSidechain.t.sol | 168 ++++++++++++++++++ 10 files changed, 311 insertions(+), 82 deletions(-) create mode 100644 test/scripts/DeployTransmuterSidechain.t.sol diff --git a/contracts/transmuter/configs/ProductionSidechain.sol b/contracts/transmuter/configs/ProductionSidechain.sol index 5cb1df89..abf4c570 100644 --- a/contracts/transmuter/configs/ProductionSidechain.sol +++ b/contracts/transmuter/configs/ProductionSidechain.sol @@ -11,7 +11,8 @@ contract ProductionSidechain { address _agToken, // USDC like tokens address liquidStablecoin, - address oracleLiquidStablecoin, + address[] memory oracleLiquidStablecoin, + uint8[] memory oracleIsMultiplied, uint256 hardCap, address dummyImplementation ) external { @@ -19,32 +20,26 @@ contract ProductionSidechain { CollateralSetupProd[] memory collaterals = new CollateralSetupProd[](1); // Liquid stablecoin { - uint64[] memory xMintFee = new uint64[](1); - xMintFee[0] = uint64(0); - - int64[] memory yMintFee = new int64[](1); - yMintFee[0] = int64(0); - - uint64[] memory xBurnFee = new uint64[](1); - xBurnFee[0] = uint64(BASE_9); - - int64[] memory yBurnFee = new int64[](1); - yBurnFee[0] = int64(0); - bytes memory oracleConfig; { bytes memory readData; { - AggregatorV3Interface[] memory circuitChainlink = new AggregatorV3Interface[](1); - uint32[] memory stalePeriods = new uint32[](1); - uint8[] memory circuitChainIsMultiplied = new uint8[](1); - uint8[] memory chainlinkDecimals = new uint8[](1); + uint256 oracleLength = oracleLiquidStablecoin.length; + if (oracleLength != oracleIsMultiplied.length) { + revert("ProductionSidechain: oracles length not equal"); + } + AggregatorV3Interface[] memory circuitChainlink = new AggregatorV3Interface[](oracleLength); + uint32[] memory stalePeriods = new uint32[](oracleLength); + uint8[] memory circuitChainIsMultiplied = new uint8[](oracleLength); + uint8[] memory chainlinkDecimals = new uint8[](oracleLength); // Oracle between liquid stablecoin and the fiat it is peg to - circuitChainlink[0] = AggregatorV3Interface(oracleLiquidStablecoin); - stalePeriods[0] = 1 days; - circuitChainIsMultiplied[0] = 1; - chainlinkDecimals[0] = 8; + for (uint256 i; i < oracleLiquidStablecoin.length; i++) { + circuitChainlink[i] = AggregatorV3Interface(oracleLiquidStablecoin[i]); + stalePeriods[i] = 1 days; + circuitChainIsMultiplied[i] = oracleIsMultiplied[i]; + chainlinkDecimals[i] = 8; + } OracleQuoteType quoteType = OracleQuoteType.UNIT; readData = abi.encode( circuitChainlink, @@ -60,9 +55,22 @@ contract ProductionSidechain { Storage.OracleReadType.STABLE, readData, targetData, - abi.encode(uint128(0), uint128(50 * BPS)) + abi.encode(uint128(30 * BPS), uint128(0)) ); } + + uint64[] memory xMintFee = new uint64[](1); + xMintFee[0] = uint64(0); + + int64[] memory yMintFee = new int64[](1); + yMintFee[0] = int64(0); + + uint64[] memory xBurnFee = new uint64[](1); + xBurnFee[0] = uint64(BASE_9); + + int64[] memory yBurnFee = new int64[](1); + yBurnFee[0] = int64(0); + collaterals[0] = CollateralSetupProd( liquidStablecoin, oracleConfig, diff --git a/foundry.toml b/foundry.toml index a569f06d..32477521 100644 --- a/foundry.toml +++ b/foundry.toml @@ -10,7 +10,7 @@ via_ir = true sizes = true optimizer = true optimizer_runs=1000 -solc_version = '0.8.23' +solc_version = '0.8.26' ffi = true fs_permissions = [ { access = "read-write", path = "./scripts/selectors.json"}, @@ -42,10 +42,19 @@ base = "${ETH_NODE_URI_BASE}" linea = "${ETH_NODE_URI_LINEA}" [etherscan] - +arbitrum = { key = "${ARBITRUM_ETHERSCAN_API_KEY}" } +gnosis = { key = "${GNOSIS_ETHERSCAN_API_KEY}" , url = "https://api.gnosisscan.io/api"} +mainnet = { key = "${MAINNET_ETHERSCAN_API_KEY}" } +optimism = { key = "${OPTIMISM_ETHERSCAN_API_KEY}" } +polygon = { key = "${POLYGON_ETHERSCAN_API_KEY}" } +avalanche = { key = "${AVALANCHE_ETHERSCAN_API_KEY}" } +celo = { key = "${CELO_ETHERSCAN_API_KEY}", url = "https://api.celoscan.io/api" } +base = { key = "${BASE_ETHERSCAN_API_KEY}", url = "https://api.basescan.org/api" } +polygon-zkevm = { key = "${POLYGONZKEVM_ETHERSCAN_API_KEY}", url = "https://api-zkevm.polygonscan.com/api" } +bsc = { key = "${BSC_ETHERSCAN_API_KEY}"} +linea = { key = "${LINEA_ETHERSCAN_API_KEY}"} [profile.dev] -optimizer = true via_ir = false src = 'contracts' gas_reports = ["*"] diff --git a/lib/forge-std b/lib/forge-std index bf660614..beb836e3 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit bf6606142994b1e47e2882ce0cd477c020d77623 +Subproject commit beb836e33f9a207f4927abb7cd09ad0afe4b3f9f diff --git a/lib/utils b/lib/utils index addaee4e..d4a8da34 160000 --- a/lib/utils +++ b/lib/utils @@ -1 +1 @@ -Subproject commit addaee4e6971172d44ca6739f3ef54d635734fc8 +Subproject commit d4a8da343cd8de28885ec5a2352d3be1cbd3b0ec diff --git a/package.json b/package.json index 7b14155b..70f93eef 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,8 @@ "coverage": "FOUNDRY_PROFILE=dev forge coverage --report lcov && yarn lcov:clean && yarn lcov:generate-html", "compile": "forge build", "compile:dev": "FOUNDRY_PROFILE=dev forge build", - "deploy": "forge script --skip test --broadcast --verify --slow -vvvv --rpc-url", - "deploy:fork": "forge script --skip test --slow --fork-url fork --broadcast -vvvv", + "deploy": "FOUNDRY_PROFILE=dev forge script --skip test --broadcast --verify --slow -vvvv --rpc-url", + "deploy:fork": "FOUNDRY_PROFILE=dev forge script --skip test --slow --fork-url fork --broadcast -vvvv", "generate": "FOUNDRY_PROFILE=dev forge script scripts/utils/GenerateSelectors.s.sol", "deploy:check": "FOUNDRY_PROFILE=dev forge script --fork-url fork scripts/test/CheckTransmuter.s.sol", "gas": "FOUNDRY_PROFILE=dev yarn test --gas-report", diff --git a/scripts/DeployTransmuterSidechain.s.sol b/scripts/DeployTransmuterSidechain.s.sol index fc87a56b..33b70f9d 100644 --- a/scripts/DeployTransmuterSidechain.s.sol +++ b/scripts/DeployTransmuterSidechain.s.sol @@ -33,16 +33,25 @@ contract DeployTransmuterSidechain is TransmuterDeploymentHelper, Helpers { // TODO uint256 chain = CHAIN_BASE; - uint256 hardCap = 1000 ether; + uint256 hardCap = 2_000_000 ether; address core = _chainToContract(chain, ContractType.CoreBorrow); - address agToken = _chainToContract(chain, ContractType.AgUSD); - StablecoinType fiat = StablecoinType.USD; + address agToken = _chainToContract(chain, ContractType.AgEUR); + address liquidStablecoin; + address[] memory oracleLiquidStablecoin; + uint8[] memory oracleIsMultiplied; + { + StablecoinType fiat = StablecoinType.EUR; + (liquidStablecoin, oracleLiquidStablecoin, oracleIsMultiplied) = _chainToLiquidStablecoinAndOracle( + chain, + fiat + ); + } // Config config = address(new ProductionSidechain()); address dummyImplementation = address(new DummyDiamondImplementation()); - (address liquidStablecoin, address oracleLiquidStablecoin) = _chainToLiquidStablecoinAndOracle(chain, fiat); + ITransmuter transmuter = _deployTransmuter( config, abi.encodeWithSelector( @@ -51,6 +60,7 @@ contract DeployTransmuterSidechain is TransmuterDeploymentHelper, Helpers { agToken, liquidStablecoin, oracleLiquidStablecoin, + oracleIsMultiplied, hardCap, dummyImplementation ) diff --git a/scripts/Helpers.s.sol b/scripts/Helpers.s.sol index daa314a6..319d2e0c 100644 --- a/scripts/Helpers.s.sol +++ b/scripts/Helpers.s.sol @@ -26,33 +26,52 @@ contract Helpers is Utils { function _chainToLiquidStablecoinAndOracle( uint256 chain, StablecoinType fiat - ) internal pure returns (address, address) { + ) + internal + pure + returns (address liquidStablecoin, address[] memory oracleAddresses, uint8[] memory oracleIsMultiplied) + { + oracleAddresses = new address[](1); + oracleIsMultiplied = new uint8[](1); if (chain == CHAIN_ARBITRUM) { - if (fiat == StablecoinType.USD) - return ( - address(0xaf88d065e77c8cC2239327C5EDb3A432268e5831), - address(0x50834F3163758fcC1Df9973b6e91f0F0F0434aD3) - ); - else revert("chain not supported"); + if (fiat == StablecoinType.USD) { + liquidStablecoin = address(0xaf88d065e77c8cC2239327C5EDb3A432268e5831); + oracleAddresses[0] = address(0x50834F3163758fcC1Df9973b6e91f0F0F0434aD3); + oracleIsMultiplied[0] = 1; + } else revert("chain not supported"); } if (chain == CHAIN_AVALANCHE) { // USDC - if (fiat == StablecoinType.USD) - return ( - address(0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E), - address(0xF096872672F44d6EBA71458D74fe67F9a77a23B9) - ); - // EURC - else return (address(0xC891EB4cbdEFf6e073e859e987815Ed1505c2ACD), address(0)); + if (fiat == StablecoinType.USD) { + liquidStablecoin = address(0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E); + oracleAddresses[0] = address(0xF096872672F44d6EBA71458D74fe67F9a77a23B9); + oracleIsMultiplied[0] = 1; + // EURC + } else { + liquidStablecoin = address(0xC891EB4cbdEFf6e073e859e987815Ed1505c2ACD); + oracleAddresses[0] = address(0); + oracleIsMultiplied[0] = 1; + } } if (chain == CHAIN_BASE) { // USDC - if (fiat == StablecoinType.USD) - return ( - address(0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913), - address(0x7e860098F58bBFC8648a4311b374B1D669a2bc6B) - ); - else revert("chain not supported"); + if (fiat == StablecoinType.USD) { + liquidStablecoin = address(0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913); + oracleAddresses[0] = address(0x7e860098F58bBFC8648a4311b374B1D669a2bc6B); + oracleIsMultiplied[0] = 1; + } else { + address[] memory oracleAddressesSize2 = new address[](2); + uint8[] memory oracleIsMultipliedSize2 = new uint8[](2); + + liquidStablecoin = address(0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42); + oracleAddressesSize2[0] = address(0xDAe398520e2B67cd3f27aeF9Cf14D93D927f8250); + oracleIsMultipliedSize2[0] = 1; + + oracleAddressesSize2[1] = address(0xc91D87E81faB8f93699ECf7Ee9B44D11e1D53F0F); + oracleIsMultipliedSize2[1] = 0; + + return (liquidStablecoin, oracleAddressesSize2, oracleIsMultipliedSize2); + } } if (chain == CHAIN_BNB) { if (fiat == StablecoinType.USD) revert("chain not supported"); @@ -60,55 +79,70 @@ contract Helpers is Utils { } if (chain == CHAIN_CELO) { // USDC - if (fiat == StablecoinType.USD) - return ( - address(0xcebA9300f2b948710d2653dD7B07f33A8B32118C), - address(0xc7A353BaE210aed958a1A2928b654938EC59DaB2) - ); - else revert("chain not supported"); + if (fiat == StablecoinType.USD) { + liquidStablecoin = address(0xcebA9300f2b948710d2653dD7B07f33A8B32118C); + oracleAddresses[0] = address(0xc7A353BaE210aed958a1A2928b654938EC59DaB2); + oracleIsMultiplied[0] = 1; + } else revert("chain not supported"); } if (chain == CHAIN_ETHEREUM) { // USDC - if (fiat == StablecoinType.USD) - return ( - address(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48), - address(0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6) - ); + if (fiat == StablecoinType.USD) { + liquidStablecoin = address(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); + oracleAddresses[0] = address(0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6); + oracleIsMultiplied[0] = 1; + } // EURC - else return (address(0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c), address(0)); + else { + liquidStablecoin = address(0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c); + oracleAddresses[0] = address(0); + oracleIsMultiplied[0] = 1; + } } if (chain == CHAIN_LINEA) { if (fiat == StablecoinType.USD) revert("chain not supported"); else revert("chain not supported"); } if (chain == CHAIN_GNOSIS) { - if (fiat == StablecoinType.USD) - return (0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83, 0x26C31ac71010aF62E6B486D1132E266D6298857D); + if (fiat == StablecoinType.USD) { + liquidStablecoin = address(0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83); + oracleAddresses[0] = address(0x26C31ac71010aF62E6B486D1132E266D6298857D); + oracleIsMultiplied[0] = 1; + } // EURe - else return (address(0xcB444e90D8198415266c6a2724b7900fb12FC56E), address(0)); + else { + liquidStablecoin = address(0xcB444e90D8198415266c6a2724b7900fb12FC56E); + oracleAddresses[0] = address(0); + oracleIsMultiplied[0] = 1; + } } if (chain == CHAIN_OPTIMISM) { // USDC - if (fiat == StablecoinType.USD) - return ( - address(0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85), - address(0x16a9FA2FDa030272Ce99B29CF780dFA30361E0f3) - ); - else revert("chain not supported"); + if (fiat == StablecoinType.USD) { + liquidStablecoin = address(0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85); + oracleAddresses[0] = address(0x16a9FA2FDa030272Ce99B29CF780dFA30361E0f3); + oracleIsMultiplied[0] = 1; + } else revert("chain not supported"); } if (chain == CHAIN_POLYGON) { // USDC - if (fiat == StablecoinType.USD) - return ( - address(0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359), - address(0xfE4A8cc5b5B2366C1B58Bea3858e81843581b2F7) - ); + if (fiat == StablecoinType.USD) { + liquidStablecoin = address(0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359); + oracleAddresses[0] = address(0xfE4A8cc5b5B2366C1B58Bea3858e81843581b2F7); + oracleIsMultiplied[0] = 1; + } // EURe - else return (address(0x18ec0A6E18E5bc3784fDd3a3634b31245ab704F6), address(0)); + else { + liquidStablecoin = address(0x18ec0A6E18E5bc3784fDd3a3634b31245ab704F6); + oracleAddresses[0] = address(0); + oracleIsMultiplied[0] = 1; + } } if (chain == CHAIN_POLYGONZKEVM) { if (fiat == StablecoinType.USD) revert("chain not supported"); else revert("chain not supported"); } else revert("chain not supported"); + + return (liquidStablecoin, oracleAddresses, oracleIsMultiplied); } } diff --git a/scripts/interaction/transmuter/Swap.s.sol b/scripts/interaction/transmuter/Swap.s.sol index cefa00ca..6af54274 100644 --- a/scripts/interaction/transmuter/Swap.s.sol +++ b/scripts/interaction/transmuter/Swap.s.sol @@ -32,7 +32,7 @@ contract SwapTransmuter is Utils, Helpers { uint256 chain = CHAIN_GNOSIS; StablecoinType fiat = StablecoinType.USD; address agToken = _chainToContract(chain, ContractType.AgUSD); - (address liquidStablecoin, ) = _chainToLiquidStablecoinAndOracle(chain, fiat); + (address liquidStablecoin, , ) = _chainToLiquidStablecoinAndOracle(chain, fiat); transmuter = ITransmuter(_chainToContract(chain, ContractType.TransmuterAgUSD)); tokenIn = agToken; decimalsIn = IERC20Metadata(agToken).decimals(); diff --git a/scripts/utils/TransmuterDeploymentHelper.s.sol b/scripts/utils/TransmuterDeploymentHelper.s.sol index f5608948..b6207116 100644 --- a/scripts/utils/TransmuterDeploymentHelper.s.sol +++ b/scripts/utils/TransmuterDeploymentHelper.s.sol @@ -105,7 +105,7 @@ contract TransmuterDeploymentHelper is Utils { address computedAddress = create2Factory.findCreate2Address(salt, initCode); console.log("Supposed to deploy: %s", address(computedAddress)); - if (computedAddress != 0x222222880e079445Df703c0604706E71a538Fd4f) revert InvalidVanityAddress(); + // if (computedAddress != 0x222222880e079445Df703c0604706E71a538Fd4f) revert InvalidVanityAddress(); transmuter = ITransmuter(create2Factory.safeCreate2(salt, initCode)); } diff --git a/test/scripts/DeployTransmuterSidechain.t.sol b/test/scripts/DeployTransmuterSidechain.t.sol new file mode 100644 index 00000000..ca679208 --- /dev/null +++ b/test/scripts/DeployTransmuterSidechain.t.sol @@ -0,0 +1,168 @@ +// 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 { Test } from "forge-std/Test.sol"; + +import { CommonUtils } from "utils/src/CommonUtils.sol"; +import "../../scripts/Constants.s.sol"; + +import "contracts/utils/Errors.sol" as Errors; +import "contracts/transmuter/Storage.sol" as Storage; +import "contracts/transmuter/libraries/LibHelpers.sol"; +import { ITransmuter } from "interfaces/ITransmuter.sol"; +import "utils/src/Constants.sol"; +import { IERC20 } from "oz/interfaces/IERC20.sol"; + +interface ITreasury { + function addMinter(address minter) external; +} + +contract DeployTransmuterSidechainTest is Test, CommonUtils { + using stdJson for string; + + ITransmuter transmuter; + IERC20 agToken; + ITreasury treasury; + uint256 fork; + address alice = vm.addr(1); + IERC20 constant collateral = IERC20(0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42); + address constant governor = 0x7DF37fc774843b678f586D55483819605228a0ae; + + function setUp() public { + fork = vm.createSelectFork(vm.envString("ETH_NODE_URI_FORK")); + vm.label(alice, "Alice"); + + // TODO update + uint256 chain = CHAIN_BASE; + agToken = IERC20(_chainToContract(chain, ContractType.AgEUR)); + treasury = ITreasury(_chainToContract(chain, ContractType.TreasuryAgEUR)); + transmuter = ITransmuter(0xBA0e73218a80C3deC1213d64873aF83B02cE0455); + + vm.prank(governor); + treasury.addMinter(address(transmuter)); + } + + /*////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + GETTERS + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/ + + function testUnit_CheckState() external { + assertEq(address(transmuter.agToken()), address(agToken)); + address[] memory collaterals = transmuter.getCollateralList(); + assertEq(collaterals.length, 1); + assertEq(collaterals[0], address(collateral)); + assertEq(transmuter.getStablecoinCap(address(collateral)), 2_000_000 ether); + assertEq(transmuter.getCollateralDecimals(address(collateral)), 6); + } + + function testUnit_CheckOracle() external { + (uint256 mint, uint256 burn, uint256 ratio, uint256 minRatio, uint256 redemption) = transmuter.getOracleValues( + address(collateral) + ); + assertEq(mint, BASE_18); + assertEq(burn, BASE_18); + assertEq(ratio, BASE_18); + assertEq(minRatio, BASE_18); + assertApproxEqRelDecimal(redemption, BASE_18, BASE_18 / 100, 18); + } + + function testUnit_SwapExactInput_Mint() external { + uint256 amount = 1_000_000 * 1e6; + uint256 agTokenAmout = 1_000_000 ether; + + deal(address(collateral), alice, amount); + + vm.startPrank(alice); + + collateral.approve(address(transmuter), amount); + transmuter.swapExactInput(amount, agTokenAmout - 1, address(collateral), address(agToken), alice, 0); + + assertGe(agToken.balanceOf(alice), agTokenAmout - 1); + assertLe(agToken.balanceOf(alice), agTokenAmout); + + (uint256 stablecoinsFromCollateral, uint256 stablecoinsIssued) = transmuter.getIssuedByCollateral( + address(collateral) + ); + assertEq(stablecoinsFromCollateral, agTokenAmout); + assertEq(stablecoinsIssued, agTokenAmout); + + vm.stopPrank(); + } + + function testUnit_SwapExactOutput_Mint() external { + uint256 amount = 1_000_000 * 1e6; + uint256 agTokenAmout = 1_000_000 ether; + + deal(address(collateral), alice, amount); + + vm.startPrank(alice); + + collateral.approve(address(transmuter), amount); + transmuter.swapExactOutput(agTokenAmout, amount, address(collateral), address(agToken), alice, 0); + + assertEq(agToken.balanceOf(alice), agTokenAmout); + + (uint256 stablecoinsFromCollateral, uint256 stablecoinsIssued) = transmuter.getIssuedByCollateral( + address(collateral) + ); + assertEq(stablecoinsFromCollateral, agTokenAmout); + assertEq(stablecoinsIssued, agTokenAmout); + + vm.stopPrank(); + } + + function testUnit_SwapExactInput_Burn() external { + uint256 amount = 1_000_000 * 1e6; + uint256 agTokenAmout = 1_000_000 ether; + + deal(address(collateral), alice, amount); + + vm.startPrank(alice); + + collateral.approve(address(transmuter), amount); + // Mint + transmuter.swapExactInput(amount, agTokenAmout - 1, address(collateral), address(agToken), alice, 0); + // Burn + transmuter.swapExactInput(agTokenAmout / 2, (amount - 1) / 2, address(agToken), address(collateral), alice, 0); + + assertEq(agToken.balanceOf(alice), agTokenAmout / 2); + assertGe(collateral.balanceOf(alice), (amount - 1) / 2); + assertLe(collateral.balanceOf(alice), amount / 2); + + (uint256 stablecoinsFromCollateral, uint256 stablecoinsIssued) = transmuter.getIssuedByCollateral( + address(collateral) + ); + assertEq(stablecoinsFromCollateral, agTokenAmout / 2); + assertEq(stablecoinsIssued, agTokenAmout / 2); + + vm.stopPrank(); + } + + function testUnit_SwapExactOutput_Burn() external { + uint256 amount = 1_000_000 * 1e6; + uint256 agTokenAmout = 1_000_000 ether; + + deal(address(collateral), alice, amount); + + vm.startPrank(alice); + + collateral.approve(address(transmuter), amount); + transmuter.swapExactInput(amount, agTokenAmout - 1, address(collateral), address(agToken), alice, 0); + transmuter.swapExactOutput(amount / 2, agTokenAmout / 2, address(agToken), address(collateral), alice, 0); + + assertGe(agToken.balanceOf(alice), (agTokenAmout - 1) / 2); + assertLe(agToken.balanceOf(alice), agTokenAmout / 2); + + assertEq(collateral.balanceOf(alice), amount / 2); + + (uint256 stablecoinsFromCollateral, uint256 stablecoinsIssued) = transmuter.getIssuedByCollateral( + address(collateral) + ); + assertEq(stablecoinsFromCollateral, agTokenAmout / 2); + assertEq(stablecoinsIssued, agTokenAmout / 2); + + vm.stopPrank(); + } +}