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

add return USD function #2358

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
22 changes: 11 additions & 11 deletions markets/legacy-market/cannonfile.test.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ from = "<%= settings.pool_owner %>"
args = [
"<%= settings.sc_pool_id %>",
[
{ marketId = "<%= extras.marketId %>", weightD18 = "1", maxDebtShareValueD18 = "1000000000000000000" },
{ marketId = "<%= settings.market_id %>", weightD18 = "1", maxDebtShareValueD18 = "1000000000000000000" },
],
]
depends = ["invoke.createPool"]
Expand All @@ -41,15 +41,15 @@ args = ["<%= settings.sc_pool_id %>"]
fromCall.func = "owner"
depends = ["invoke.createPool"]

[invoke.registerRewardsDistributor]
target = ["v3.CoreProxy"]
func = "registerRewardsDistributor"
args = [
"<%= settings.sc_pool_id %>",
"<%= imports.v2x.contracts.ProxySynthetix.address %>",
"<%= contracts.SNXDistributor.address %>",
]
#[invoke.registerRewardsDistributor]
Copy link
Contributor

@noisekit noisekit Feb 18, 2025

Choose a reason for hiding this comment

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

Do these comments need to stay?

#target = ["v3.CoreProxy"]
#func = "registerRewardsDistributor"
#args = [
# "<%= settings.sc_pool_id %>",
# "<%= imports.v2x.contracts.ProxySynthetix.address %>",
# "<%= contracts.SNXDistributor.address %>",
#]

fromCall.func = "owner"
#fromCall.func = "owner"

depends = ["invoke.createPool", "contract.SNXDistributor"]
#depends = ["invoke.createPool"]
2 changes: 1 addition & 1 deletion markets/legacy-market/cannonfile.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ defaultValue = "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"
defaultValue = "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"

[setting.v2x_package]
defaultValue = "synthetix:2.101.2"
defaultValue = "synthetix:2.101.3"

[setting.v3_package]
defaultValue = "synthetix:3.3.15"
Expand Down
29 changes: 29 additions & 0 deletions markets/legacy-market/contracts/LegacyMarket.sol
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,35 @@ contract LegacyMarket is ILegacyMarket, Ownable, UUPSImplementation, IMarket, IE
return 0;
}

/**
* @inheritdoc ILegacyMarket
*/
function returnUSD(uint256 amount) external override {
if (pauseStablecoinConversion) {
revert Paused();
}

if (amount == 0) {
revert ParameterError.InvalidParameter("amount", "Should be non-zero");
}

address sender = ERC2771Context._msgSender();

// get synthetix v2x addresses
IERC20 oldUSD = IERC20(v2xResolver.getAddress("ProxysUSD"));
ISynthetix oldSynthetix = ISynthetix(v2xResolver.getAddress("Synthetix"));
IIssuer iss = IIssuer(v2xResolver.getAddress("Issuer"));

// retrieve the v3 sUSD from the user so we can burn it
v3System.depositMarketUsd(marketId, sender, amount);

// now issue new synths and send them to the user
oldSynthetix.issueSynths(amount);
oldUSD.transfer(sender, amount);

emit ReturnedUSD(ERC2771Context._msgSender(), amount);
}

/**
* @inheritdoc ILegacyMarket
*/
Expand Down
17 changes: 17 additions & 0 deletions markets/legacy-market/contracts/interfaces/ILegacyMarket.sol
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ interface ILegacyMarket {
*/
event ConvertedUSD(address indexed account, uint256 amount);

/**
* @notice Emitted after a call to `returnUSD`, moving debt from v3 to v2x.
* @param account the address of the address which provided the v3 sUSD for conversion
* @param amount the amount of v3 sUSD burnt, and the amount of v2x sUSD minted
*/
event ReturnedUSD(address indexed account, uint256 amount);

/**
* @notice Emitted after a call to `setPauseStablecoinConversion`
* @param sender the address setting the stablecoin conversion pause status
Expand All @@ -65,6 +72,16 @@ interface ILegacyMarket {
*/
event PauseMigrationSet(address indexed sender, bool paused);

/**
* @notice Called by anyone with {amount} v3 sUSD to convert {amount} v3 sUSD to {amount} v2x sUSD.
* The v3 sUSD will be burned (thereby reducing the sUSD total supply and v3 system size), and v2x sUSD will be minted.
* Any user who has v3 sUSD can call this function.
* Requirements:
* * User must first approve() the legacy market contract to spend the user's v3 sUSD
* @param amount the quantity to convert
*/
function returnUSD(uint256 amount) external;

/**
* @notice Called by anyone with {amount} sUSD to convert {amount} sUSD to {amount} snxUSD.
* The sUSD will be burned (thereby reducing the sUSD total supply and v2x system size), and snxUSD will be minted.
Expand Down
122 changes: 80 additions & 42 deletions markets/legacy-market/test/integration/LegacyMarket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ describe('LegacyMarket', function () {
let synthetixDebtShare: ethers.Contract;
let liquidationRewards: ethers.Contract;
let rewardEscrow: ethers.Contract;
let snxDistributor: ethers.Contract;

let v3System: ethers.Contract;
let v3Account: ethers.Contract;
Expand Down Expand Up @@ -118,11 +117,6 @@ describe('LegacyMarket', function () {
provider
);

snxDistributor = new ethers.Contract(
outputs.contracts.SNXDistributor.address,
outputs.contracts.SNXDistributor.abi
);

await rewardEscrow
.connect(await getImpersonatedSigner(provider, await rewardEscrow.owner()))
.setPermittedEscrowCreator(await owner.getAddress(), true);
Expand Down Expand Up @@ -218,6 +212,86 @@ describe('LegacyMarket', function () {
});
});

describe('returnUSD()', async () => {
before(restore);

before('approve', async () => {
await susdToken.connect(snxStaker).approve(market.address, ethers.constants.MaxUint256);
});

describe('when some snxUSD is available and collateral has been migrated', async () => {
const convertedAmount = wei(1);

before('do migration', async () => {
await snxToken.connect(snxStaker).approve(market.address, ethers.constants.MaxUint256);
await market.connect(snxStaker).migrate(migratedAccountId);

// sanity
assertBn.gte(
await v3System.getWithdrawableMarketUsd(await market.marketId()),
convertedAmount.toBN()
);
});

before('do convert', async () => {
await (await market.connect(snxStaker).convertUSD(convertedAmount.toBN())).wait();
});

it('fails when returnUSD argument is 0', async () => {
await assertRevert(market.connect(snxStaker).returnUSD(0), 'InvalidParameter(\\"amount\\"');
});

it('fails when insufficient source balance', async () => {
// transfer away some of the sUSD so that we can see what happens when there is not balance
await susdToken.connect(snxStaker).transfer(await owner.getAddress(), wei(500).toBN());
await assertRevert(
market.connect(snxStaker).convertUSD(wei(501).toBN()),
'Error(\\"Insufficient balance after any settlement owing\\")'
);
});

describe('when invoked', async () => {
let txn: ethers.providers.TransactionReceipt;

let beforeMarketBalance: Wei;

before('record priors', async () => {
beforeMarketBalance = wei(await v3System.getMarketNetIssuance(await market.marketId()));
});

before('when invoked', async () => {
await (
await v3Usd.connect(snxStaker).approve(market.address, convertedAmount.toBN())
).wait();
txn = await (await market.connect(snxStaker).returnUSD(convertedAmount.toBN())).wait();
});

it('mints v2x USD', async () => {
assertBn.equal(await susdToken.balanceOf(await snxStaker.getAddress()), wei(500).toBN());
});

it('burns v3 USD', async () => {
assertBn.equal(await v3Usd.balanceOf(await snxStaker.getAddress()), 0);
});

it('increased market balance', async () => {
assertBn.equal(
await v3System.getMarketNetIssuance(await market.marketId()),
beforeMarketBalance.sub(1).toBN()
);
});

it('emitted an event', async () => {
await assertEvent(
txn,
`ReturnedUSD("${snxStakerAddress}", ${convertedAmount.toBN().toString()})`,
market
);
});
});
});
});

function testMigrate(doMigrateCall: () => Promise<ethers.providers.TransactionResponse>) {
describe('with a fake account with escrow entries and liquidation rewards', async () => {
before('create escrow entries', async () => {
Expand Down Expand Up @@ -438,42 +512,6 @@ describe('LegacyMarket', function () {
ethers.utils.parseEther('1000')
);
});

it('rewards available for distributed', async () => {
assertBn.equal(
await v3System
.connect(owner)
.callStatic.claimRewards(
accountId,
await v3System.getPreferredPool(),
snxToken.address,
snxDistributor.address
),
ethers.utils.parseEther('10')
);
});

describe('claim rewards from rewards distributor', async () => {
let beforeBalance: ethers.BigNumberish;
before('claim', async () => {
beforeBalance = await snxToken.balanceOf(await owner.getAddress());
await v3System
.connect(owner)
.claimRewards(
accountId,
await v3System.getPreferredPool(),
snxToken.address,
snxDistributor.address
);
});

it('should have collateral in account', async () => {
assertBn.equal(
(await snxToken.balanceOf(await owner.getAddress())).sub(beforeBalance),
ethers.utils.parseEther('10')
);
});
});
});
});
});
Expand Down
2 changes: 1 addition & 1 deletion markets/treasury-market/cannonfile.test.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ defaultValue = "1"
defaultValue = "<%= imports.v3.contracts.CollateralMock.address %>"

[setting.v3_package]
defaultValue = "synthetix:3.10.3-testable"
defaultValue = "synthetix:3.10.4-testable"

depends = []

Expand Down