Skip to content

Commit c60c265

Browse files
authored
Merge pull request #86 from OffchainLabs/non-18-decimals
Handle non-18-decimal fee tokens in token bridge creator
2 parents 92c3cab + 33a2061 commit c60c265

File tree

13 files changed

+443
-168
lines changed

13 files changed

+443
-168
lines changed

.github/workflows/build-test.yml

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
- name: Setup node/yarn
2222
uses: actions/setup-node@v3
2323
with:
24-
node-version: 16
24+
node-version: 18
2525
cache: 'yarn'
2626
cache-dependency-path: '**/yarn.lock'
2727

@@ -53,7 +53,7 @@ jobs:
5353
- name: Setup node/yarn
5454
uses: actions/setup-node@v3
5555
with:
56-
node-version: 16
56+
node-version: 18
5757
cache: 'yarn'
5858
cache-dependency-path: '**/yarn.lock'
5959

@@ -82,7 +82,7 @@ jobs:
8282
- name: Setup node/yarn
8383
uses: actions/setup-node@v3
8484
with:
85-
node-version: 16
85+
node-version: 18
8686
cache: 'yarn'
8787
cache-dependency-path: '**/yarn.lock'
8888

@@ -111,11 +111,13 @@ jobs:
111111
l3-node: true
112112
no-token-bridge: true
113113
no-l3-token-bridge: true
114+
token-bridge-branch: '${{ github.head_ref }}'
115+
nitro-testnode-ref: node-18
114116

115117
- name: Setup node/yarn
116118
uses: actions/setup-node@v3
117119
with:
118-
node-version: 16
120+
node-version: 18
119121
cache: 'yarn'
120122
cache-dependency-path: '**/yarn.lock'
121123

@@ -148,11 +150,56 @@ jobs:
148150
args: --l3-fee-token
149151
no-token-bridge: true
150152
no-l3-token-bridge: true
153+
token-bridge-branch: '${{ github.head_ref }}'
154+
nitro-testnode-ref: node-18
151155

152156
- name: Setup node/yarn
153157
uses: actions/setup-node@v3
154158
with:
155-
node-version: 16
159+
node-version: 18
160+
cache: 'yarn'
161+
cache-dependency-path: '**/yarn.lock'
162+
163+
- name: Install packages
164+
run: yarn
165+
166+
- name: Compile contracts
167+
run: yarn build
168+
169+
- name: Deploy creator and create token bridge
170+
run: yarn deploy:local:token-bridge
171+
172+
- name: Verify deployed token bridge
173+
run: yarn test:tokenbridge:deployment
174+
175+
- name: Verify creation code generation
176+
run: yarn test:creation-code
177+
178+
- name: Test e2e orbit token bridge actions
179+
run: yarn hardhat test test-e2e/orbitTokenBridge.ts
180+
181+
test-e2e-6-decimals-fee-token:
182+
name: Test e2e on 6-decimals custom fee token chain
183+
runs-on: ubuntu-latest
184+
steps:
185+
- uses: actions/checkout@v3
186+
with:
187+
submodules: recursive
188+
189+
- uses: OffchainLabs/actions/run-nitro-test-node@main
190+
with:
191+
l3-node: true
192+
args: --l3-fee-token --l3-fee-token-decimals 6
193+
no-token-bridge: true
194+
no-l3-token-bridge: true
195+
token-bridge-branch: '${{ github.head_ref }}'
196+
nitro-contracts-branch: 'develop'
197+
nitro-testnode-ref: 'non18-decimal-token-node-18'
198+
199+
- name: Setup node/yarn
200+
uses: actions/setup-node@v3
201+
with:
202+
node-version: 18
156203
cache: 'yarn'
157204
cache-dependency-path: '**/yarn.lock'
158205

.gitmodules

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@
44
[submodule "lib/nitro-contracts"]
55
path = lib/nitro-contracts
66
url = https://github.com/OffchainLabs/nitro-contracts.git
7-
branch = v1.2.1
7+
branch = develop
8+

contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol

Lines changed: 85 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
L2TemplateAddresses,
1010
IERC20Inbox,
1111
IERC20,
12+
ERC20,
1213
SafeERC20
1314
} from "./L1TokenBridgeRetryableSender.sol";
1415
import {L1GatewayRouter} from "./gateway/L1GatewayRouter.sol";
@@ -216,7 +217,7 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable {
216217
// deployment mappings should not be updated in case of resend
217218
bool isResend = (inboxToL1Deployment[inbox].router != address(0));
218219

219-
bool isUsingFeeToken = _getFeeToken(inbox) != address(0);
220+
address feeToken = _getFeeToken(inbox);
220221

221222
// store L2 addresses before deployments
222223
L1DeploymentAddresses memory l1Deployment;
@@ -233,7 +234,7 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable {
233234
l2Deployment.router = _getProxyAddress(OrbitSalts.L2_ROUTER, chainId);
234235
l2Deployment.standardGateway = _getProxyAddress(OrbitSalts.L2_STANDARD_GATEWAY, chainId);
235236
l2Deployment.customGateway = _getProxyAddress(OrbitSalts.L2_CUSTOM_GATEWAY, chainId);
236-
if (!isUsingFeeToken) {
237+
if (feeToken == address(0)) {
237238
l2Deployment.wethGateway = _getProxyAddress(OrbitSalts.L2_WETH_GATEWAY, chainId);
238239
l2Deployment.weth = _getProxyAddress(OrbitSalts.L2_WETH, chainId);
239240
}
@@ -256,7 +257,7 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable {
256257
if (!isResend) {
257258
// l1 router deployment block
258259
{
259-
address routerTemplate = isUsingFeeToken
260+
address routerTemplate = feeToken != address(0)
260261
? address(l1Templates.feeTokenBasedRouterTemplate)
261262
: address(l1Templates.routerTemplate);
262263
l1Deployment.router = _deployProxyWithSalt(
@@ -266,7 +267,7 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable {
266267

267268
// l1 standard gateway deployment block
268269
{
269-
address template = isUsingFeeToken
270+
address template = feeToken != address(0)
270271
? address(l1Templates.feeTokenBasedStandardGatewayTemplate)
271272
: address(l1Templates.standardGatewayTemplate);
272273

@@ -289,7 +290,7 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable {
289290

290291
// l1 custom gateway deployment block
291292
{
292-
address template = isUsingFeeToken
293+
address template = feeToken != address(0)
293294
? address(l1Templates.feeTokenBasedCustomGatewayTemplate)
294295
: address(l1Templates.customGatewayTemplate);
295296

@@ -307,7 +308,7 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable {
307308
}
308309

309310
// l1 weth gateway deployment block
310-
if (!isUsingFeeToken) {
311+
if (feeToken == address(0)) {
311312
L1WethGateway wethGateway = L1WethGateway(
312313
payable(
313314
_deployProxyWithSalt(
@@ -338,14 +339,37 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable {
338339

339340
// deploy factory and then L2 contracts through L2 factory, using 2 retryables calls
340341
// we do not care if it is a resend or not, if the L2 deployment already exists it will simply fail on L2
341-
_deployL2Factory(inbox, gasPriceBid, isUsingFeeToken);
342-
if (isUsingFeeToken) {
342+
_deployL2Factory(inbox, gasPriceBid, feeToken);
343+
344+
RetryableParams memory retryableParams = RetryableParams(
345+
inbox,
346+
canonicalL2FactoryAddress,
347+
msg.sender,
348+
msg.sender,
349+
maxGasForContracts,
350+
gasPriceBid,
351+
0
352+
);
353+
354+
if (feeToken != address(0)) {
343355
// transfer fee tokens to inbox to pay for 2nd retryable
344-
address feeToken = _getFeeToken(inbox);
345-
uint256 fee = maxGasForContracts * gasPriceBid;
346-
IERC20(feeToken).safeTransferFrom(msg.sender, inbox, fee);
356+
retryableParams.feeTokenTotalFeeAmount =
357+
_getScaledAmount(feeToken, maxGasForContracts * gasPriceBid);
358+
IERC20(feeToken).safeTransferFrom(
359+
msg.sender, inbox, retryableParams.feeTokenTotalFeeAmount
360+
);
347361
}
348362

363+
L2TemplateAddresses memory l2TemplateAddress = L2TemplateAddresses(
364+
l2RouterTemplate,
365+
l2StandardGatewayTemplate,
366+
l2CustomGatewayTemplate,
367+
feeToken != address(0) ? address(0) : l2WethGatewayTemplate,
368+
feeToken != address(0) ? address(0) : l2WethTemplate,
369+
address(l1Templates.upgradeExecutor),
370+
l2MulticallTemplate
371+
);
372+
349373
// alias rollup owner if it is a contract
350374
address l2RollupOwner = rollupOwner.code.length == 0
351375
? rollupOwner
@@ -354,30 +378,13 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable {
354378
// sweep the balance to send the retryable and refund the difference
355379
// it is known that any eth previously in this contract can be extracted
356380
// tho it is not expected that this contract will have any eth
357-
retryableSender.sendRetryable{value: isUsingFeeToken ? 0 : address(this).balance}(
358-
RetryableParams(
359-
inbox,
360-
canonicalL2FactoryAddress,
361-
msg.sender,
362-
msg.sender,
363-
maxGasForContracts,
364-
gasPriceBid
365-
),
366-
L2TemplateAddresses(
367-
l2RouterTemplate,
368-
l2StandardGatewayTemplate,
369-
l2CustomGatewayTemplate,
370-
isUsingFeeToken ? address(0) : l2WethGatewayTemplate,
371-
isUsingFeeToken ? address(0) : l2WethTemplate,
372-
address(l1Templates.upgradeExecutor),
373-
l2MulticallTemplate
374-
),
381+
_sendRetryableToCreateContracts(
382+
retryableParams,
383+
l2TemplateAddress,
375384
l1Deployment,
376-
l2Deployment.standardGateway,
385+
l2Deployment,
377386
l2RollupOwner,
378-
msg.sender,
379-
upgradeExecutor,
380-
isUsingFeeToken
387+
upgradeExecutor
381388
);
382389

383390
// deployment mappings should not be updated in case of resend
@@ -390,6 +397,27 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable {
390397
}
391398
}
392399

400+
function _sendRetryableToCreateContracts(
401+
RetryableParams memory retryableParams,
402+
L2TemplateAddresses memory l2TemplateAddress,
403+
L1DeploymentAddresses memory l1Deployment,
404+
L2DeploymentAddresses memory l2Deployment,
405+
address l2RollupOwner,
406+
address upgradeExecutor
407+
) internal {
408+
retryableSender.sendRetryable{
409+
value: retryableParams.feeTokenTotalFeeAmount > 0 ? 0 : address(this).balance
410+
}(
411+
retryableParams,
412+
l2TemplateAddress,
413+
l1Deployment,
414+
l2Deployment.standardGateway,
415+
l2RollupOwner,
416+
msg.sender,
417+
upgradeExecutor
418+
);
419+
}
420+
393421
/**
394422
* @notice Rollup owner can override deployment
395423
*/
@@ -416,16 +444,16 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable {
416444
return inboxToL1Deployment[inbox].router;
417445
}
418446

419-
function _deployL2Factory(address inbox, uint256 gasPriceBid, bool isUsingFeeToken) internal {
447+
function _deployL2Factory(address inbox, uint256 gasPriceBid, address feeToken) internal {
420448
// encode L2 factory bytecode
421449
bytes memory deploymentData =
422450
CreationCodeHelper.getCreationCodeFor(l2TokenBridgeFactoryTemplate.code);
423451

424-
if (isUsingFeeToken) {
452+
if (feeToken != address(0)) {
425453
// transfer fee tokens to inbox to pay for 1st retryable
426-
address feeToken = _getFeeToken(inbox);
427454
uint256 retryableFee = gasLimitForL2FactoryDeployment * gasPriceBid;
428-
IERC20(feeToken).safeTransferFrom(msg.sender, inbox, retryableFee);
455+
uint256 scaledRetryableFee = _getScaledAmount(feeToken, retryableFee);
456+
IERC20(feeToken).safeTransferFrom(msg.sender, inbox, scaledRetryableFee);
429457

430458
IERC20Inbox(inbox).createRetryableTicket(
431459
address(0),
@@ -435,7 +463,7 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable {
435463
msg.sender,
436464
gasLimitForL2FactoryDeployment,
437465
gasPriceBid,
438-
retryableFee,
466+
scaledRetryableFee,
439467
deploymentData
440468
);
441469
} else {
@@ -569,6 +597,25 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable {
569597
{
570598
return address(new TransparentUpgradeableProxy{salt: salt}(logic, admin, bytes("")));
571599
}
600+
601+
/**
602+
* @notice Scale amount to the fee token's decimals. Ie. amount of 1e18 will be scaled to 1e6 if fee token has 6 decimals like USDC.
603+
*/
604+
function _getScaledAmount(address feeToken, uint256 amount) internal view returns (uint256) {
605+
uint8 decimals = ERC20(feeToken).decimals();
606+
if (decimals == 18) {
607+
return amount;
608+
}
609+
if (decimals < 18) {
610+
uint256 scaledAmount = amount / (10 ** (18 - decimals));
611+
// round up if necessary
612+
if (scaledAmount * (10 ** (18 - decimals)) < amount) {
613+
scaledAmount++;
614+
}
615+
return scaledAmount;
616+
}
617+
return amount * (10 ** (decimals - 18));
618+
}
572619
}
573620

574621
interface IERC20Bridge {

0 commit comments

Comments
 (0)