Skip to content

Commit 4c1b3ea

Browse files
committed
feat: subgraph migration to L2 sending only the owner's tokens (OZ M-01)
1 parent b59c798 commit 4c1b3ea

File tree

9 files changed

+526
-548
lines changed

9 files changed

+526
-548
lines changed

contracts/discovery/L1GNS.sol

Lines changed: 82 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,13 @@ contract L1GNS is GNS, L1GNSV1Storage, L1ArbitrumMessenger {
2828

2929
/// @dev Emitted when a subgraph was sent to L2 through the bridge
3030
event SubgraphSentToL2(uint256 indexed _subgraphID, address indexed _l2Owner);
31-
/// @dev Emitted when the address of the Arbitrum Inbox was updated
32-
event ArbitrumInboxAddressUpdated(address _inbox);
3331

34-
/**
35-
* @dev sets the addresses for L1 inbox provided by Arbitrum
36-
* @param _inbox Address of the Inbox that is part of the Arbitrum Bridge
37-
*/
38-
function setArbitrumInboxAddress(address _inbox) external onlyGovernor {
39-
arbitrumInboxAddress = _inbox;
40-
emit ArbitrumInboxAddressUpdated(_inbox);
41-
}
32+
/// @dev Emitted when a curator's balance for a subgraph was sent to L2
33+
event CuratorBalanceSentToL2(
34+
uint256 indexed _subgraphID,
35+
address indexed _curator,
36+
uint256 _tokens
37+
);
4238

4339
/**
4440
* @notice Send a subgraph's data and tokens to L2.
@@ -72,29 +68,41 @@ contract L1GNS is GNS, L1GNSV1Storage, L1ArbitrumMessenger {
7268
subgraphData.disabled = true;
7369
subgraphData.vSignal = 0;
7470

75-
bytes memory extraData = _encodeSubgraphDataForL2(_subgraphID, _l2Owner, subgraphData);
71+
// We send only the subgraph owner's tokens and nsignal to L2,
72+
// and for everyone else we set the withdrawableGRT so that they can choose
73+
// to withdraw or migrate their signal.
74+
uint256 ownerNSignal = subgraphData.curatorNSignal[msg.sender];
75+
uint256 totalSignal = subgraphData.nSignal;
7676

77-
bytes memory data = abi.encode(_maxSubmissionCost, extraData);
78-
IGraphToken grt = graphToken();
79-
ITokenGateway gateway = graphTokenGateway();
80-
grt.approve(address(gateway), curationTokens);
81-
gateway.outboundTransfer{ value: msg.value }({
82-
_token: address(grt),
83-
_to: counterpartGNSAddress,
84-
_amount: curationTokens,
85-
_maxGas: _maxGas,
86-
_gasPriceBid: _gasPriceBid,
87-
_data: data
88-
});
77+
// Get owner share of tokens to be sent to L2
78+
uint256 tokensForL2 = ownerNSignal.mul(curationTokens).div(totalSignal);
79+
// This leaves the subgraph as if it was deprecated,
80+
// so other curators can withdraw:
81+
subgraphData.curatorNSignal[msg.sender] = 0;
82+
subgraphData.nSignal = totalSignal.sub(ownerNSignal);
83+
subgraphData.withdrawableGRT = curationTokens.sub(tokensForL2);
84+
85+
bytes memory extraData = abi.encode(
86+
uint8(IL2GNS.L1MessageCodes.RECEIVE_SUBGRAPH_CODE),
87+
abi.encode(_subgraphID, _l2Owner)
88+
);
89+
90+
_sendTokensAndMessageToL2GNS(
91+
tokensForL2,
92+
_maxGas,
93+
_gasPriceBid,
94+
_maxSubmissionCost,
95+
extraData
96+
);
8997

9098
subgraphData.reserveRatioDeprecated = 0;
9199
_burnNFT(_subgraphID);
92100
emit SubgraphSentToL2(_subgraphID, _l2Owner);
93101
}
94102

95103
/**
96-
* @notice Claim the balance for a curator's signal in a subgraph that was
97-
* migrated to L2, by sending a retryable ticket to the L2GNS.
104+
* @notice Send the balance for a curator's signal in a subgraph that was
105+
* migrated to L2, using the L1GraphTokenGateway.
98106
* The balance will be claimed for a beneficiary address, as this method can be
99107
* used by curators that use a contract address in L1 that may not exist in L2.
100108
* This will set the curator's signal on L1 to zero, so the caller must ensure
@@ -105,87 +113,73 @@ contract L1GNS is GNS, L1GNSV1Storage, L1ArbitrumMessenger {
105113
* @param _maxGas Max gas to use for the L2 retryable ticket
106114
* @param _gasPriceBid Gas price bid for the L2 retryable ticket
107115
* @param _maxSubmissionCost Max submission cost for the L2 retryable ticket
108-
* @return The sequence ID for the retryable ticket, as returned by the Arbitrum inbox.
109116
*/
110-
function claimCuratorBalanceToBeneficiaryOnL2(
117+
function sendCuratorBalanceToBeneficiaryOnL2(
111118
uint256 _subgraphID,
112119
address _beneficiary,
113120
uint256 _maxGas,
114121
uint256 _gasPriceBid,
115122
uint256 _maxSubmissionCost
116-
) external payable notPartialPaused returns (bytes memory) {
123+
) external payable notPartialPaused {
117124
require(subgraphMigratedToL2[_subgraphID], "!MIGRATED");
118125

119126
// The Arbitrum bridge will check this too, we just check here for an early exit
120127
require(_maxSubmissionCost != 0, "NO_SUBMISSION_COST");
121128

122-
L2GasParams memory gasParams = L2GasParams(_maxSubmissionCost, _maxGas, _gasPriceBid);
123-
124-
uint256 curatorNSignal = getCuratorSignal(_subgraphID, msg.sender);
129+
SubgraphData storage subgraphData = _getSubgraphData(_subgraphID);
130+
uint256 curatorNSignal = subgraphData.curatorNSignal[msg.sender];
125131
require(curatorNSignal != 0, "NO_SIGNAL");
126-
bytes memory outboundCalldata = getClaimCuratorBalanceOutboundCalldata(
127-
_subgraphID,
128-
curatorNSignal,
129-
msg.sender,
130-
_beneficiary
132+
uint256 subgraphNSignal = subgraphData.nSignal;
133+
require(subgraphNSignal != 0, "NO_SUBGRAPH_SIGNAL");
134+
135+
uint256 tokensForL2 = curatorNSignal.mul(subgraphData.withdrawableGRT).div(subgraphNSignal);
136+
bytes memory extraData = abi.encode(
137+
uint8(IL2GNS.L1MessageCodes.RECEIVE_CURATOR_BALANCE_CODE),
138+
abi.encode(_subgraphID, _beneficiary)
131139
);
132140

133-
// Similarly to withdrawing from a deprecated subgraph,
134-
// we remove the curator's signal from the subgraph.
135-
SubgraphData storage subgraphData = _getSubgraphData(_subgraphID);
141+
// Set the subgraph as if the curator had withdrawn their tokens
136142
subgraphData.curatorNSignal[msg.sender] = 0;
137-
subgraphData.nSignal = subgraphData.nSignal.sub(curatorNSignal);
138-
139-
uint256 seqNum = sendTxToL2({
140-
_inbox: arbitrumInboxAddress,
141-
_to: counterpartGNSAddress,
142-
_user: msg.sender,
143-
_l1CallValue: msg.value,
144-
_l2CallValue: 0,
145-
_l2GasParams: gasParams,
146-
_data: outboundCalldata
147-
});
148-
149-
return abi.encode(seqNum);
150-
}
151-
152-
/**
153-
* @notice Get the outbound calldata that will be sent to L2
154-
* when calling claimCuratorBalanceToBeneficiaryOnL2.
155-
* This can be useful to estimate the L2 retryable ticket parameters.
156-
* @param _subgraphID Subgraph ID
157-
* @param _curatorNSignal Curator's signal in the subgraph
158-
* @param _curator Curator address
159-
* @param _beneficiary Address that will own the signal in L2
160-
*/
161-
function getClaimCuratorBalanceOutboundCalldata(
162-
uint256 _subgraphID,
163-
uint256 _curatorNSignal,
164-
address _curator,
165-
address _beneficiary
166-
) public pure returns (bytes memory) {
167-
return
168-
abi.encodeWithSelector(
169-
IL2GNS.claimL1CuratorBalanceToBeneficiary.selector,
170-
_subgraphID,
171-
_curator,
172-
_curatorNSignal,
173-
_beneficiary
174-
);
143+
subgraphData.nSignal = subgraphNSignal.sub(curatorNSignal);
144+
subgraphData.withdrawableGRT = subgraphData.withdrawableGRT.sub(tokensForL2);
145+
146+
// Send the tokens and data to L2 using the L1GraphTokenGateway
147+
_sendTokensAndMessageToL2GNS(
148+
tokensForL2,
149+
_maxGas,
150+
_gasPriceBid,
151+
_maxSubmissionCost,
152+
extraData
153+
);
175154
}
176155

177156
/**
178-
* @dev Encodes the subgraph data as callhook parameters
179-
* for the L2 migration.
180-
* @param _subgraphID Subgraph ID
181-
* @param _l2Owner Owner of the subgraph on L2
182-
* @param _subgraphData Subgraph data
157+
* @notice Sends a message to the L2GNS with some extra data,
158+
* also sending some tokens, using the L1GraphTokenGateway.
159+
* @param _tokens Amount of tokens to send to L2
160+
* @param _maxGas Max gas to use for the L2 retryable ticket
161+
* @param _gasPriceBid Gas price bid for the L2 retryable ticket
162+
* @param _maxSubmissionCost Max submission cost for the L2 retryable ticket
163+
* @param _extraData Extra data for the callhook on L2GNS
183164
*/
184-
function _encodeSubgraphDataForL2(
185-
uint256 _subgraphID,
186-
address _l2Owner,
187-
SubgraphData storage _subgraphData
188-
) internal view returns (bytes memory) {
189-
return abi.encode(_subgraphID, _l2Owner, _subgraphData.nSignal);
165+
function _sendTokensAndMessageToL2GNS(
166+
uint256 _tokens,
167+
uint256 _maxGas,
168+
uint256 _gasPriceBid,
169+
uint256 _maxSubmissionCost,
170+
bytes memory _extraData
171+
) internal {
172+
bytes memory data = abi.encode(_maxSubmissionCost, _extraData);
173+
IGraphToken grt = graphToken();
174+
ITokenGateway gateway = graphTokenGateway();
175+
grt.approve(address(gateway), _tokens);
176+
gateway.outboundTransfer{ value: msg.value }(
177+
address(grt),
178+
counterpartGNSAddress,
179+
_tokens,
180+
_maxGas,
181+
_gasPriceBid,
182+
data
183+
);
190184
}
191185
}

contracts/discovery/L1GNSStorage.sol

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ pragma abicoder v2;
1010
* reduce the size of the gap accordingly.
1111
*/
1212
abstract contract L1GNSV1Storage {
13-
/// Address of the Arbitrum DelayedInbox
14-
address public arbitrumInboxAddress;
1513
/// True for subgraph IDs that have been migrated to L2
1614
mapping(uint256 => bool) public subgraphMigratedToL2;
1715
/// @dev Storage gap to keep storage slots fixed in future versions

contracts/l2/curation/IL2Curation.sol

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,11 @@ interface IL2Curation {
1212
* only during an L1-L2 migration).
1313
* @param _subgraphDeploymentID Subgraph deployment pool from where to mint signal
1414
* @param _tokensIn Amount of Graph Tokens to deposit
15-
* @param _signalOutMin Expected minimum amount of signal to receive
1615
* @return Signal minted
1716
*/
18-
function mintTaxFree(
19-
bytes32 _subgraphDeploymentID,
20-
uint256 _tokensIn,
21-
uint256 _signalOutMin
22-
) external returns (uint256);
17+
function mintTaxFree(bytes32 _subgraphDeploymentID, uint256 _tokensIn)
18+
external
19+
returns (uint256);
2320

2421
/**
2522
* @notice Calculate amount of signal that can be bought with tokens in a curation pool,

contracts/l2/curation/L2Curation.sol

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -231,23 +231,21 @@ contract L2Curation is CurationV2Storage, GraphUpgradeable, IL2Curation {
231231
* only during an L1-L2 migration).
232232
* @param _subgraphDeploymentID Subgraph deployment pool from where to mint signal
233233
* @param _tokensIn Amount of Graph Tokens to deposit
234-
* @param _signalOutMin Expected minimum amount of signal to receive
235234
* @return Signal minted
236235
*/
237-
function mintTaxFree(
238-
bytes32 _subgraphDeploymentID,
239-
uint256 _tokensIn,
240-
uint256 _signalOutMin
241-
) external override notPartialPaused onlyGNS returns (uint256) {
236+
function mintTaxFree(bytes32 _subgraphDeploymentID, uint256 _tokensIn)
237+
external
238+
override
239+
notPartialPaused
240+
onlyGNS
241+
returns (uint256)
242+
{
242243
// Need to deposit some funds
243244
require(_tokensIn != 0, "Cannot deposit zero tokens");
244245

245246
// Exchange GRT tokens for GCS of the subgraph pool (no tax)
246247
uint256 signalOut = _tokensToSignal(_subgraphDeploymentID, _tokensIn);
247248

248-
// Slippage protection
249-
require(signalOut >= _signalOutMin, "Slippage protection");
250-
251249
address curator = msg.sender;
252250
CurationPool storage curationPool = pools[_subgraphDeploymentID];
253251

contracts/l2/discovery/IL2GNS.sol

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ import { ICallhookReceiver } from "../../gateway/ICallhookReceiver.sol";
88
* @title Interface for the L2GNS contract.
99
*/
1010
interface IL2GNS is ICallhookReceiver {
11+
enum L1MessageCodes {
12+
RECEIVE_SUBGRAPH_CODE,
13+
RECEIVE_CURATOR_BALANCE_CODE
14+
}
15+
1116
/**
1217
* @dev The SubgraphL2MigrationData struct holds information
1318
* about a subgraph related to its migration from L1 to L2.
@@ -34,29 +39,4 @@ interface IL2GNS is ICallhookReceiver {
3439
bytes32 _subgraphMetadata,
3540
bytes32 _versionMetadata
3641
) external;
37-
38-
/**
39-
* @notice Deprecate a subgraph that was migrated from L1, but for which
40-
* the migration was never finished. Anyone can call this function after a certain amount of
41-
* blocks have passed since the subgraph was migrated, if the subgraph owner didn't
42-
* call finishSubgraphMigrationFromL1. In L2GNS this timeout is the FINISH_MIGRATION_TIMEOUT constant.
43-
* @param _subgraphID Subgraph ID
44-
*/
45-
function deprecateSubgraphMigratedFromL1(uint256 _subgraphID) external;
46-
47-
/**
48-
* @notice Claim curator balance belonging to a curator from L1.
49-
* This will be credited to the a beneficiary on L2, and can only be called
50-
* from the GNS on L1 through a retryable ticket.
51-
* @param _subgraphID Subgraph on which to claim the balance
52-
* @param _curator Curator who owns the balance on L1
53-
* @param _balance Balance of the curator from L1
54-
* @param _beneficiary Address of an L2 beneficiary for the balance
55-
*/
56-
function claimL1CuratorBalanceToBeneficiary(
57-
uint256 _subgraphID,
58-
address _curator,
59-
uint256 _balance,
60-
address _beneficiary
61-
) external;
6242
}

0 commit comments

Comments
 (0)