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 1 commit
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).
9 changes: 9 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,12 @@ library SkyDeploy {
sky = address(new Sky());
ScriptTools.switchOwner(sky, deployer, owner);
}

function deploySupplySync(
address mkr,
address sky,
address owner
) internal returns (address supplySync) {
supplySync = address(new SupplySync(mkr, sky, owner));
}
}
20 changes: 20 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,11 @@ interface MkrSkyLike {
function rate() external view returns (uint256);
}

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

interface MkrLike {
function authority() external view returns (address);
}
Expand All @@ -54,4 +60,18 @@ 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(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);
}
}
54 changes: 54 additions & 0 deletions src/SupplySync.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: AGPL-3.0-or-later

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

// Copyright (C) 2023 Dai Foundation
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shoulde be 2024

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done

//
// 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;
}

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

constructor(address mkr_, address sky_, address owner) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Considering the converter doesn't have the conversion value hardcoded, I wouldn't do it here. My suggestion would be to actually pass the converter as the constructor param, and then you have the 3 values.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

ok done

mkr = GemLike(mkr_);
sky = GemLike(sky_);

// 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 {
uint256 mkrSupplyInSky = mkr.totalSupply() * 24_000;
uint256 skyBalance = sky.balanceOf(address(this));

unchecked {
if (mkrSupplyInSky > skyBalance) {
sky.mint(address(this), mkrSupplyInSky - skyBalance);
} else {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think I'd prefer an elseif, so we avoid to burn 0 if sync is called multiple times. But it is really a very subjective thing, not really important gas wise.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

ok, changed to that and also added return values so it's easier to use it in a bot/cron-job.

sky.burn(address(this), skyBalance - mkrSupplyInSky);
}
}
}
}
106 changes: 106 additions & 0 deletions test/integration/SupplySync.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// SPDX-License-Identifier: AGPL-3.0-or-later

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 rely(address) external;
telome marked this conversation as resolved.
Show resolved Hide resolved
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(address(MKR), address(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(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));

sync.sync();

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

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

function testSZeroSkyInSync() public {
oldchili marked this conversation as resolved.
Show resolved Hide resolved
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(true, 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