Skip to content

Commit 347e5b6

Browse files
author
justin j. moses
authored
SIP-140 Exchange with tracking for initiator (Synthetixio#1279)
1 parent 3c696da commit 347e5b6

File tree

7 files changed

+164
-13
lines changed

7 files changed

+164
-13
lines changed

contracts/BaseSynthetix.sol

+10
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,16 @@ contract BaseSynthetix is IERC20, ExternStateToken, MixinResolver, ISynthetix {
292292
return issuer().burnSynthsToTargetOnBehalf(burnForAddress, messageSender);
293293
}
294294

295+
function exchangeWithTrackingForInitiator(
296+
bytes32,
297+
uint,
298+
bytes32,
299+
address,
300+
bytes32
301+
) external returns (uint amountReceived) {
302+
_notImplemented();
303+
}
304+
295305
function exchangeWithVirtual(
296306
bytes32,
297307
uint,

contracts/Synthetix.sol

+23
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,29 @@ contract Synthetix is BaseSynthetix {
7272
);
7373
}
7474

75+
// SIP-140 The initiating user of this exchange will receive the proceeds of the exchange
76+
// Note: this function may have unintended consequences if not understood correctly. Please
77+
// read SIP-140 for more information on the use-case
78+
function exchangeWithTrackingForInitiator(
79+
bytes32 sourceCurrencyKey,
80+
uint sourceAmount,
81+
bytes32 destinationCurrencyKey,
82+
address originator,
83+
bytes32 trackingCode
84+
) external exchangeActive(sourceCurrencyKey, destinationCurrencyKey) optionalProxy returns (uint amountReceived) {
85+
return
86+
exchanger().exchangeWithTracking(
87+
messageSender,
88+
sourceCurrencyKey,
89+
sourceAmount,
90+
destinationCurrencyKey,
91+
// solhint-disable avoid-tx-origin
92+
tx.origin,
93+
originator,
94+
trackingCode
95+
);
96+
}
97+
7598
function settle(bytes32 currencyKey)
7699
external
77100
optionalProxy

contracts/interfaces/ISynthetix.sol

+8
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,14 @@ interface ISynthetix {
7373
bytes32 trackingCode
7474
) external returns (uint amountReceived);
7575

76+
function exchangeWithTrackingForInitiator(
77+
bytes32 sourceCurrencyKey,
78+
uint sourceAmount,
79+
bytes32 destinationCurrencyKey,
80+
address originator,
81+
bytes32 trackingCode
82+
) external returns (uint amountReceived);
83+
7684
function exchangeOnBehalfWithTracking(
7785
address exchangeForAddress,
7886
bytes32 sourceCurrencyKey,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
pragma solidity ^0.5.16;
2+
3+
import "../interfaces/IAddressResolver.sol";
4+
import "../interfaces/ISynthetix.sol";
5+
6+
contract MockThirdPartyExchangeContract {
7+
IAddressResolver public resolver;
8+
9+
constructor(IAddressResolver _resolver) public {
10+
resolver = _resolver;
11+
}
12+
13+
function exchange(
14+
bytes32 src,
15+
uint amount,
16+
bytes32 dest
17+
) external {
18+
ISynthetix synthetix = ISynthetix(resolver.getAddress("Synthetix"));
19+
20+
synthetix.exchangeWithTrackingForInitiator(src, amount, dest, address(this), "TRACKING_CODE");
21+
}
22+
}

publish/releases.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@
244244
"minor": 46
245245
},
246246
"sources": ["Exchanger", "Synthetix"],
247-
"sips": [138, 139]
247+
"sips": [138, 139, 140]
248248
},
249249
{
250250
"name": "Alnitak (Optimism)",
@@ -254,6 +254,6 @@
254254
},
255255
"ovm": true,
256256
"sources": ["Exchanger", "MintableSynthetix"],
257-
"sips": [138, 139]
257+
"sips": [138, 139, 140]
258258
}
259259
]

test/contracts/BaseSynthetix.js

+19-8
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ contract('BaseSynthetix', async accounts => {
9090
'exchangeOnBehalf',
9191
'exchangeOnBehalfWithTracking',
9292
'exchangeWithTracking',
93+
'exchangeWithTrackingForInitiator',
9394
'exchangeWithVirtual',
9495
'issueMaxSynths',
9596
'issueMaxSynthsOnBehalf',
@@ -142,15 +143,25 @@ contract('BaseSynthetix', async accounts => {
142143

143144
describe('non-basic functions always revert', () => {
144145
const amount = 100;
145-
it('ExchangeWithVirtual should revert no matter who the caller is', async () => {
146+
it('exchangeWithVirtual should revert no matter who the caller is', async () => {
146147
await onlyGivenAddressCanInvoke({
147148
fnc: baseSynthetix.exchangeWithVirtual,
148149
accounts,
149150
args: [sUSD, amount, sAUD, toBytes32('AGGREGATOR')],
150151
reason: 'Cannot be run on this layer',
151152
});
152153
});
153-
it('Mint should revert no matter who the caller is', async () => {
154+
155+
it('exchangeWithTrackingForInitiator should revert no matter who the caller is', async () => {
156+
await onlyGivenAddressCanInvoke({
157+
fnc: baseSynthetix.exchangeWithTrackingForInitiator,
158+
accounts,
159+
args: [sUSD, amount, sAUD, owner, toBytes32('AGGREGATOR')],
160+
reason: 'Cannot be run on this layer',
161+
});
162+
});
163+
164+
it('mint should revert no matter who the caller is', async () => {
154165
await onlyGivenAddressCanInvoke({
155166
fnc: baseSynthetix.mint,
156167
accounts,
@@ -159,31 +170,31 @@ contract('BaseSynthetix', async accounts => {
159170
});
160171
});
161172

162-
it('LiquidateDelinquentAccount should revert no matter who the caller is', async () => {
173+
it('liquidateDelinquentAccount should revert no matter who the caller is', async () => {
163174
await onlyGivenAddressCanInvoke({
164175
fnc: baseSynthetix.liquidateDelinquentAccount,
165176
accounts,
166177
args: [account1, amount],
167178
reason: 'Cannot be run on this layer',
168179
});
169180
});
170-
it('MintSecondary should revert no matter who the caller is', async () => {
181+
it('mintSecondary should revert no matter who the caller is', async () => {
171182
await onlyGivenAddressCanInvoke({
172183
fnc: baseSynthetix.mintSecondary,
173184
accounts,
174185
args: [account1, amount],
175186
reason: 'Cannot be run on this layer',
176187
});
177188
});
178-
it('MintSecondaryRewards should revert no matter who the caller is', async () => {
189+
it('mintSecondaryRewards should revert no matter who the caller is', async () => {
179190
await onlyGivenAddressCanInvoke({
180191
fnc: baseSynthetix.mintSecondaryRewards,
181192
accounts,
182193
args: [amount],
183194
reason: 'Cannot be run on this layer',
184195
});
185196
});
186-
it('BurnSecondary should revert no matter who the caller is', async () => {
197+
it('burnSecondary should revert no matter who the caller is', async () => {
187198
await onlyGivenAddressCanInvoke({
188199
fnc: baseSynthetix.burnSecondary,
189200
accounts,
@@ -322,7 +333,7 @@ contract('BaseSynthetix', async accounts => {
322333

323334
it('exchangeOnBehalf is called with the right arguments ', async () => {
324335
await baseSynthetix.exchangeOnBehalf(account1, currencyKey1, amount1, currencyKey2, {
325-
from: owner,
336+
from: msgSender,
326337
});
327338
assert.equal(smockExchanger.smocked.exchangeOnBehalf.calls[0][0], account1);
328339
assert.equal(smockExchanger.smocked.exchangeOnBehalf.calls[0][1], msgSender);
@@ -338,7 +349,7 @@ contract('BaseSynthetix', async accounts => {
338349
currencyKey2,
339350
account2,
340351
trackingCode,
341-
{ from: owner }
352+
{ from: msgSender }
342353
);
343354
assert.equal(smockExchanger.smocked.exchangeWithTracking.calls[0][0], msgSender);
344355
assert.equal(smockExchanger.smocked.exchangeWithTracking.calls[0][1], currencyKey1);

test/contracts/Synthetix.js

+80-3
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ const {
2424
} = require('../..');
2525

2626
contract('Synthetix', async accounts => {
27-
const [sAUD, sEUR] = ['sAUD', 'sEUR'].map(toBytes32);
27+
const [sAUD, sEUR, sUSD, sETH] = ['sAUD', 'sEUR', 'sUSD', 'sETH'].map(toBytes32);
2828

29-
const [, owner, account1, account2] = accounts;
29+
const [, owner, account1, account2, account3] = accounts;
3030

3131
let synthetix,
3232
exchangeRates,
@@ -36,7 +36,9 @@ contract('Synthetix', async accounts => {
3636
rewardEscrowV2,
3737
oracle,
3838
addressResolver,
39-
systemStatus;
39+
systemStatus,
40+
sUSDContract,
41+
sETHContract;
4042

4143
before(async () => {
4244
({
@@ -48,6 +50,8 @@ contract('Synthetix', async accounts => {
4850
RewardEscrow: rewardEscrow,
4951
RewardEscrowV2: rewardEscrowV2,
5052
SupplySchedule: supplySchedule,
53+
SynthsUSD: sUSDContract,
54+
SynthsETH: sETHContract,
5155
} = await setupAllContracts({
5256
accounts,
5357
synths: ['sUSD', 'sETH', 'sEUR', 'sAUD'],
@@ -105,6 +109,7 @@ contract('Synthetix', async accounts => {
105109
beforeEach(async () => {
106110
smockExchanger = await smockit(artifacts.require('Exchanger').abi);
107111
smockExchanger.smocked.exchangeWithVirtual.will.return.with(() => ['1', account1]);
112+
smockExchanger.smocked.exchangeWithTracking.will.return.with(() => ['1']);
108113
await addressResolver.importAddresses(
109114
['Exchanger'].map(toBytes32),
110115
[smockExchanger.address],
@@ -130,6 +135,24 @@ contract('Synthetix', async accounts => {
130135
assert.equal(smockExchanger.smocked.exchangeWithVirtual.calls[0][4], msgSender);
131136
assert.equal(smockExchanger.smocked.exchangeWithVirtual.calls[0][5], trackingCode);
132137
});
138+
139+
it('exchangeWithTrackingForInitiator is called with the right arguments ', async () => {
140+
await synthetix.exchangeWithTrackingForInitiator(
141+
currencyKey1,
142+
amount1,
143+
currencyKey2,
144+
account2,
145+
trackingCode,
146+
{ from: account3 }
147+
);
148+
assert.equal(smockExchanger.smocked.exchangeWithTracking.calls[0][0], account3);
149+
assert.equal(smockExchanger.smocked.exchangeWithTracking.calls[0][1], currencyKey1);
150+
assert.equal(smockExchanger.smocked.exchangeWithTracking.calls[0][2].toString(), amount1);
151+
assert.equal(smockExchanger.smocked.exchangeWithTracking.calls[0][3], currencyKey2);
152+
assert.equal(smockExchanger.smocked.exchangeWithTracking.calls[0][4], account3); // destination address (tx.origin)
153+
assert.equal(smockExchanger.smocked.exchangeWithTracking.calls[0][5], account2);
154+
assert.equal(smockExchanger.smocked.exchangeWithTracking.calls[0][6], trackingCode);
155+
});
133156
});
134157

135158
describe('mint() - inflationary supply minting', async () => {
@@ -390,4 +413,58 @@ contract('Synthetix', async accounts => {
390413
assert.bnEqual(await synthetix.balanceOf(rewardEscrow.address), 0);
391414
});
392415
});
416+
417+
describe('Using a contract to invoke exchangeWithTrackingForInitiator', () => {
418+
describe('when a third party contract is setup to exchange synths', () => {
419+
let contractExample;
420+
let amountOfsUSD;
421+
beforeEach(async () => {
422+
amountOfsUSD = toUnit('100');
423+
424+
const MockThirdPartyExchangeContract = artifacts.require('MockThirdPartyExchangeContract');
425+
426+
// create a contract
427+
contractExample = await MockThirdPartyExchangeContract.new(addressResolver.address);
428+
429+
// ensure rates are set
430+
await updateRatesWithDefaults({ exchangeRates, oracle, debtCache });
431+
432+
// issue sUSD from the owner
433+
await synthetix.issueSynths(amountOfsUSD, { from: owner });
434+
435+
// transfer the sUSD to the contract
436+
await sUSDContract.transfer(contractExample.address, toUnit('100'), { from: owner });
437+
});
438+
439+
describe('when Barrie invokes the exchange function on the contract', () => {
440+
let txn;
441+
beforeEach(async () => {
442+
// Barrie has no sETH to start
443+
assert.equal(await sETHContract.balanceOf(account3), '0');
444+
445+
txn = await contractExample.exchange(sUSD, amountOfsUSD, sETH, { from: account3 });
446+
});
447+
it('then Barrie has the synths in her account', async () => {
448+
assert.bnGt(await sETHContract.balanceOf(account3), toUnit('0.01'));
449+
});
450+
it('and the contract has none', async () => {
451+
assert.equal(await sETHContract.balanceOf(contractExample.address), '0');
452+
});
453+
it('and the event emitted indicates that Barrie was the destinationAddress', async () => {
454+
const logs = artifacts.require('Synthetix').decodeLogs(txn.receipt.rawLogs);
455+
assert.eventEqual(
456+
logs.find(log => log.event === 'SynthExchange'),
457+
'SynthExchange',
458+
{
459+
account: contractExample.address,
460+
fromCurrencyKey: sUSD,
461+
fromAmount: amountOfsUSD,
462+
toCurrencyKey: sETH,
463+
toAddress: account3,
464+
}
465+
);
466+
});
467+
});
468+
});
469+
});
393470
});

0 commit comments

Comments
 (0)