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

Supply Sync #16

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ It is a converter between `Mkr` and `Sky` (both ways). Using the `mint` and `bur
**Note:** if one of the tokens removes `mint` capabilities to this contract, it means that the path which gives that token to the user won't be available.

**Note 2:** In the MKR -> SKY conversion, if the user passes a `wad` amount not multiple of `rate`, it causes that a dusty value will be lost.

### SupplySync

A contract with permissionless functionality that syncs the SKY supply to include also the MKR supply (thus MKR acts as wrapper of SKY).
8 changes: 8 additions & 0 deletions deploy/SkyDeploy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { ScriptTools } from "dss-test/ScriptTools.sol";

import { Sky } from "src/Sky.sol";
import { MkrSky } from "src/MkrSky.sol";
import { SupplySync } from "src/SupplySync.sol";

import { SkyInstance } from "./SkyInstance.sol";

Expand All @@ -46,4 +47,11 @@ library SkyDeploy {
sky = address(new Sky());
ScriptTools.switchOwner(sky, deployer, owner);
}

function deploySupplySync(
address mkrSky,
address owner
) internal returns (address supplySync) {
supplySync = address(new SupplySync(mkrSky, owner));
}
}
22 changes: 22 additions & 0 deletions deploy/SkyInit.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { SkyInstance } from "./SkyInstance.sol";

interface SkyLike {
function rely(address) external;
function allowance(address, address) external view returns (uint256);
}

interface MkrSkyLike {
Expand All @@ -29,6 +30,12 @@ interface MkrSkyLike {
function rate() external view returns (uint256);
}

interface SupplySyncLike {
function mkr() external view returns (address);
function sky() external view returns (address);
function rate() external view returns (uint256);
}

interface MkrLike {
function authority() external view returns (address);
}
Expand All @@ -54,4 +61,19 @@ library SkyInit {
dss.chainlog.setAddress("SKY", instance.sky);
dss.chainlog.setAddress("MKR_SKY", instance.mkrSky);
}

function initSupplySync(
DssInstance memory dss,
address supplySync
) internal {
SkyLike sky = SkyLike(dss.chainlog.getAddress("SKY"));

require(SupplySyncLike(supplySync).mkr() == dss.chainlog.getAddress("MCD_GOV"), "SkyInit/mkr-does-not-match");
require(SupplySyncLike(supplySync).sky() == address(sky), "SkyInit/sky-does-not-match");
require(SupplySyncLike(supplySync).rate() == 24_000, "SkyInit/rate-does-not-match");
require(sky.allowance(supplySync, dss.chainlog.getAddress("MCD_PAUSE_PROXY")) == type(uint256).max, "SkyInit/allowance-not-set");

sky.rely(supplySync);
dss.chainlog.setAddress("SKY_SUPPLY_SYNC", supplySync);
}
}
65 changes: 65 additions & 0 deletions src/SupplySync.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// SPDX-License-Identifier: AGPL-3.0-or-later

/// MkrSky.sol -- Mkr/Sky Exchanger

// Copyright (C) 2024 Dai Foundation
//
// 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.21;

interface GemLike {
function totalSupply() external view returns (uint256);
function balanceOf(address) external view returns (uint256);
function approve(address, uint256) external;
function mint(address, uint256) external;
function burn(address, uint256) external;
}

interface MkrSkyLike {
function mkr() external view returns (address);
function sky() external view returns (address);
function rate() external view returns (uint256);
}

contract SupplySync {
GemLike public immutable mkr;
GemLike public immutable sky;
uint256 public immutable rate;

constructor(address mkrSky, address owner) {
mkr = GemLike(MkrSkyLike(mkrSky).mkr());
sky = GemLike(MkrSkyLike(mkrSky).sky());
rate = MkrSkyLike(mkrSky).rate();

// Allow owner (pause proxy) to burn the sky in this contract, if ever needed to wind down
sky.approve(owner, type(uint256).max);
}

function sync() external returns (bool isMint, uint256 amount) {
uint256 mkrSupplyInSky = mkr.totalSupply() * rate;
uint256 skyBalance = sky.balanceOf(address(this));

unchecked {
if (mkrSupplyInSky > skyBalance) {
isMint = true;
amount = mkrSupplyInSky - skyBalance;
sky.mint(address(this), amount);
} else if (mkrSupplyInSky < skyBalance) {
amount = skyBalance - mkrSupplyInSky;
sky.burn(address(this), amount);
}
}
}
}
122 changes: 122 additions & 0 deletions test/integration/SupplySync.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// 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.21;

import "dss-test/DssTest.sol";
import { SupplySync } from "src/SupplySync.sol";
import { SkyDeploy } from "deploy/SkyDeploy.sol";
import { SkyInit } from "deploy/SkyInit.sol";

interface GemLike {
function totalSupply() external view returns (uint256);
function balanceOf(address) external view returns (uint256);
function allowance(address, address) external view returns (uint256);
function burn(address, uint256) external;
}

interface SkyLike is GemLike {
function wards(address) external view returns (uint256);
function deny(address) external;
}

contract SupplySyncTest is DssTest {
DssInstance dss;

address PAUSE_PROXY;
GemLike MKR;
SkyLike SKY;

SupplySync sync;

function setUp() public {
vm.createSelectFork(vm.envString("ETH_RPC_URL"));

dss = MCD.loadFromChainlog(0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F);

PAUSE_PROXY = dss.chainlog.getAddress("MCD_PAUSE_PROXY");
MKR = GemLike(dss.chainlog.getAddress("MCD_GOV"));
SKY = SkyLike(dss.chainlog.getAddress("SKY"));

sync = SupplySync(SkyDeploy.deploySupplySync(dss.chainlog.getAddress("MKR_SKY"), PAUSE_PROXY));
vm.startPrank(PAUSE_PROXY);
SkyInit.initSupplySync(dss, address(sync));
vm.stopPrank();
}

function testDeployAndInit() public {
assertEq(address(sync.mkr()), address(MKR));
assertEq(address(sync.sky()), address(SKY));
assertEq(sync.rate(), 24_000);
assertEq(SKY.allowance(address(sync), PAUSE_PROXY), type(uint256).max);
assertEq(SKY.wards(address(sync)), 1);
assertEq(dss.chainlog.getAddress("SKY_SUPPLY_SYNC"), address(sync));
}

function _checkSync(bool isExpectedMint, uint256 expectedChange) internal {
uint256 mkrSupply = MKR.totalSupply();
uint256 skySupplyBefore = SKY.totalSupply();
uint256 syncBalanceBefore = SKY.balanceOf(address(sync));

(bool isMint, uint256 amount) = sync.sync();

uint256 syncBalanceAfter = SKY.balanceOf(address(sync));

assertEq(syncBalanceAfter, mkrSupply * 24_000);
assertEq(isMint, isExpectedMint);
assertEq(amount, expectedChange);
if (isExpectedMint) {
assertEq(syncBalanceAfter, syncBalanceBefore + expectedChange);
assertEq(SKY.totalSupply(), skySupplyBefore + expectedChange);
} else {
assertEq(syncBalanceAfter, syncBalanceBefore - expectedChange);
assertEq(SKY.totalSupply(), skySupplyBefore - expectedChange);
}
}

function testZeroSkyInSync() public {
deal(address(SKY), address(sync), 0);
_checkSync(true, MKR.totalSupply() * 24_000);
}

function testLessSkyInSync() public {
deal(address(SKY), address(sync), MKR.totalSupply() * 24_000 - 1234);
_checkSync(true, 1234);
}

function testMoreSkyInSync() public {
deal(address(SKY), address(sync), MKR.totalSupply() * 24_000 + 1234);
_checkSync(false, 1234);
}

function testExactSkyInSync() public {
deal(address(SKY), address(sync), MKR.totalSupply() * 24_000);
_checkSync(false, 0);
}

function testWindDown() public {
deal(address(SKY), address(sync), 1234);

vm.startPrank(PAUSE_PROXY);
SKY.burn(address(sync), SKY.balanceOf(address(sync)));
SKY.deny(address(sync)); // revoke mint allowance
vm.stopPrank();

assertEq(SKY.balanceOf(address(sync)), 0);
vm.expectRevert("Sky/not-authorized");
sync.sync();
}
}
Loading