Skip to content

Commit

Permalink
Merge branch 'develop' into fast-conf-port
Browse files Browse the repository at this point in the history
  • Loading branch information
gzeoneth authored Jul 3, 2024
2 parents d876e2a + 7aae8fa commit 5cf93d2
Show file tree
Hide file tree
Showing 49 changed files with 1,977 additions and 160 deletions.
34 changes: 33 additions & 1 deletion .github/workflows/contract-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ jobs:
- name: Setup nodejs
uses: actions/setup-node@v2
with:
node-version: '18'
node-version: 18
cache: 'yarn'
cache-dependency-path: '**/yarn.lock'

Expand Down Expand Up @@ -199,3 +199,35 @@ jobs:

- name: Run e2e tests
run: yarn test:e2e
test-e2e-fee-token-6-decimals:
name: Test e2e fee token with 6 decimals
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
submodules: recursive

- uses: OffchainLabs/actions/run-nitro-test-node@main
with:
l3-node: true
args: --l3-fee-token --l3-fee-token-decimals 6
no-token-bridge: true
no-l3-token-bridge: true
nitro-contracts-branch: '${{ github.head_ref }}'
nitro-testnode-ref: 'non18-decimal-token'

- name: Setup node/yarn
uses: actions/setup-node@v3
with:
node-version: 16
cache: 'yarn'
cache-dependency-path: '**/yarn.lock'

- name: Install packages
run: yarn

- name: Compile contracts
run: yarn build

- name: Run e2e tests
run: yarn test:e2e
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ yarn build

## License

Nitro is currently licensed under a [Business Source License](./LICENSE), similar to our friends at Uniswap and Aave, with an "Additional Use Grant" to ensure that everyone can have full comfort using and running nodes on all public Arbitrum chains.
Nitro is currently licensed under a [Business Source License](./LICENSE.md), similar to our friends at Uniswap and Aave, with an "Additional Use Grant" to ensure that everyone can have full comfort using and running nodes on all public Arbitrum chains.

The Additional Use Grant also permits the deployment of the Nitro software, in a permissionless fashion and without cost, as a new blockchain provided that the chain settles to either Arbitrum One or Arbitrum Nova.

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
},
"private": false,
"devDependencies": {
"@arbitrum/sdk": "^3.1.3",
"@arbitrum/sdk": "^3.4.1",
"@ethersproject/providers": "^5.7.2",
"@nomiclabs/hardhat-ethers": "npm:hardhat-deploy-ethers@^0.3.0-beta.13",
"@nomiclabs/hardhat-etherscan": "^3.1.0",
Expand Down
35 changes: 30 additions & 5 deletions scripts/deploymentUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import {
bytecode as UpgradeExecutorBytecode,
} from '@offchainlabs/upgrade-executor/build/contracts/src/UpgradeExecutor.sol/UpgradeExecutor.json'
import { Toolkit4844 } from '../test/contract/toolkit4844'
import { ArbOwner__factory, ArbSys__factory } from '../build/types'
import {
ArbOwner__factory,
ArbSys__factory,
CacheManager__factory,
} from '../build/types'

const INIT_CACHE_SIZE = 536870912
const INIT_DECAY = 10322197911
Expand Down Expand Up @@ -244,20 +248,41 @@ export async function deployAndSetCacheManager(
chainOwnerWallet: any,
verify: boolean = true
) {
const cacheManager = await deployContract(
const cacheManagerLogic = await deployContract(
'CacheManager',
chainOwnerWallet,
[INIT_CACHE_SIZE, INIT_DECAY],
[],
verify
)

const proxyAdmin = await deployContract(
'ProxyAdmin',
chainOwnerWallet,
[],
verify
)

const cacheManagerProxy = await deployContract(
'TransparentUpgradeableProxy',
chainOwnerWallet,
[cacheManagerLogic.address, proxyAdmin.address, '0x'],
verify
)

const cacheManager = CacheManager__factory.connect(
cacheManagerProxy.address,
chainOwnerWallet
)

await (await cacheManager.initialize(INIT_CACHE_SIZE, INIT_DECAY)).wait()

const arbOwner = ArbOwner__factory.connect(
ARB_OWNER_ADDRESS,
chainOwnerWallet
)
await (await arbOwner.addWasmCacheManager(cacheManager.address)).wait()
await (await arbOwner.addWasmCacheManager(cacheManagerProxy.address)).wait()

return cacheManager
return cacheManagerProxy
}

// Check if we're deploying to an Arbitrum chain
Expand Down
28 changes: 27 additions & 1 deletion scripts/rollupCreation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { run } from 'hardhat'
import { abi as rollupCreatorAbi } from '../build/contracts/src/rollup/RollupCreator.sol/RollupCreator.json'
import { config, maxDataSize } from './config'
import { BigNumber, Signer } from 'ethers'
import { IERC20__factory } from '../build/types'
import { ERC20, ERC20__factory, IERC20__factory } from '../build/types'
import { sleep } from './testSetup'
import { promises as fs } from 'fs'
import { _isRunningOnArbitrum } from './deploymentUtils'
Expand Down Expand Up @@ -85,6 +85,10 @@ export async function createRollup(
let feeCost = ethers.utils.parseEther('0.13')
if (feeToken != ethers.constants.AddressZero) {
// in case fees are paid via fee token, then approve rollup cretor to spend required amount
feeCost = await _getPrescaledAmount(
ERC20__factory.connect(feeToken, signer),
feeCost
)
await (
await IERC20__factory.connect(feeToken, signer).approve(
rollupCreator.address,
Expand Down Expand Up @@ -338,3 +342,25 @@ async function _getDevRollupConfig(
})
}
}

async function _getPrescaledAmount(
nativeToken: ERC20,
amount: BigNumber
): Promise<BigNumber> {
const decimals = BigNumber.from(await nativeToken.decimals())
if (decimals.lt(BigNumber.from(18))) {
const scalingFactor = BigNumber.from(10).pow(
BigNumber.from(18).sub(decimals)
)
let prescaledAmount = amount.div(scalingFactor)
// round up if needed
if (prescaledAmount.mul(scalingFactor).lt(amount)) {
prescaledAmount = prescaledAmount.add(BigNumber.from(1))
}
return prescaledAmount
} else if (decimals.gt(BigNumber.from(18))) {
return amount.mul(BigNumber.from(10).pow(decimals.sub(BigNumber.from(18))))
}

return amount
}
7 changes: 4 additions & 3 deletions scripts/testSetup.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { JsonRpcProvider } from '@ethersproject/providers'
import { L1Network, L2Network } from '@arbitrum/sdk'
import { execSync } from 'child_process'
import { Bridge__factory } from '@arbitrum/sdk/dist/lib/abi/factories/Bridge__factory'
import { RollupAdminLogic__factory } from '@arbitrum/sdk/dist/lib/abi/factories/RollupAdminLogic__factory'
import { Bridge__factory, RollupAdminLogic__factory } from '../build/types'

export function sleep(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms))
Expand Down Expand Up @@ -77,6 +76,8 @@ export const getLocalNetworks = async (
isCustom: true,
name: 'ArbLocal',
partnerChainID: l1NetworkInfo.chainId,
partnerChainIDs: [],
blockTime: 1,
retryableLifetimeSeconds: 7 * 24 * 60 * 60,
nitroGenesisBlock: 0,
nitroGenesisL1Block: 0,
Expand All @@ -86,4 +87,4 @@ export const getLocalNetworks = async (
l1Network,
l2Network,
}
}
}
2 changes: 1 addition & 1 deletion slither.db.json

Large diffs are not rendered by default.

20 changes: 16 additions & 4 deletions src/bridge/AbsInbox.sol
Original file line number Diff line number Diff line change
Expand Up @@ -231,11 +231,14 @@ abstract contract AbsInbox is DelegateCallAware, PausableUpgradeable, IInboxBase
uint256 amount,
bytes calldata data
) internal returns (uint256) {
// ensure the user's deposit alone will make submission succeed
if (amount < (maxSubmissionCost + l2CallValue + gasLimit * maxFeePerGas)) {
// Ensure the user's deposit alone will make submission succeed.
// In case of native token having non-18 decimals: 'amount' is denominated in native token's decimals. All other
// value params - l2CallValue, maxSubmissionCost and maxFeePerGas are denominated in child chain's native 18 decimals.
uint256 amountToBeMintedOnL2 = _fromNativeTo18Decimals(amount);
if (amountToBeMintedOnL2 < (maxSubmissionCost + l2CallValue + gasLimit * maxFeePerGas)) {
revert InsufficientValue(
maxSubmissionCost + l2CallValue + gasLimit * maxFeePerGas,
amount
amountToBeMintedOnL2
);
}

Expand Down Expand Up @@ -308,7 +311,7 @@ abstract contract AbsInbox is DelegateCallAware, PausableUpgradeable, IInboxBase
abi.encodePacked(
uint256(uint160(to)),
l2CallValue,
amount,
_fromNativeTo18Decimals(amount),
maxSubmissionCost,
uint256(uint160(excessFeeRefundAddress)),
uint256(uint160(callValueRefundAddress)),
Expand Down Expand Up @@ -347,6 +350,15 @@ abstract contract AbsInbox is DelegateCallAware, PausableUpgradeable, IInboxBase
virtual
returns (uint256);

/// @notice get amount of ETH/token to mint on child chain based on provided value.
/// In case of ETH-based rollup this amount will always equal the provided
/// value. In case of ERC20-based rollup where native token has number of
/// decimals different thatn 18, amount will be re-adjusted to reflect 18
/// decimals used for native currency on child chain.
/// @dev provided value has to be less than 'type(uint256).max/10**(18-decimalsIn)'
/// or otherwise it will overflow.
function _fromNativeTo18Decimals(uint256 value) internal view virtual returns (uint256);

/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
Expand Down
13 changes: 11 additions & 2 deletions src/bridge/AbsOutbox.sol
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,10 @@ abstract contract AbsOutbox is DelegateCallAware, IOutbox {
) internal {
emit OutBoxTransactionExecuted(to, l2Sender, 0, outputId);

// get amount to unlock based on provided value. It might differ in case
// of native token which uses number of decimals different than 18
uint256 amountToUnlock = _getAmountToUnlock(value);

// we temporarily store the previous values so the outbox can naturally
// unwind itself when there are nested calls to `executeTransaction`
L2ToL1Context memory prevContext = context;
Expand All @@ -215,11 +219,11 @@ abstract contract AbsOutbox is DelegateCallAware, IOutbox {
l1Block: uint96(l1Block),
timestamp: uint128(l2Timestamp),
outputId: bytes32(outputId),
withdrawalAmount: _amountToSetInContext(value)
withdrawalAmount: _amountToSetInContext(amountToUnlock)
});

// set and reset vars around execution so they remain valid during call
executeBridgeCall(to, value, data);
executeBridgeCall(to, amountToUnlock, data);

context = prevContext;
}
Expand Down Expand Up @@ -311,6 +315,11 @@ abstract contract AbsOutbox is DelegateCallAware, IOutbox {
/// @return default 'amount' in case of ERC20-based rollup is type(uint256).max, or 0 in case of ETH-based rollup
function _defaultContextAmount() internal pure virtual returns (uint256);

/// @notice based on provided value, get amount of ETH/token to unlock. In case of ETH-based rollup this amount
/// will always equal the provided value. In case of ERC20-based rollup, amount will be re-adjusted to
/// reflect the number of decimals used by native token, in case it is different than 18.
function _getAmountToUnlock(uint256 value) internal view virtual returns (uint256);

/// @notice value to be set for 'amount' field in L2ToL1Context during L2 to L1 transaction execution.
/// In case of ERC20-based rollup this is the amount of native token being withdrawn. In case of standard ETH-based
/// rollup this amount shall always be 0, because amount of ETH being withdrawn can be read from msg.value.
Expand Down
33 changes: 30 additions & 3 deletions src/bridge/ERC20Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,18 @@ pragma solidity ^0.8.4;
import "./AbsBridge.sol";
import "./IERC20Bridge.sol";
import "../libraries/AddressAliasHelper.sol";
import {InvalidTokenSet, CallTargetNotAllowed, CallNotAllowed} from "../libraries/Error.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {
InvalidTokenSet,
CallTargetNotAllowed,
CallNotAllowed,
NativeTokenDecimalsTooLarge
} from "../libraries/Error.sol";
import {DecimalsConverterHelper} from "../libraries/DecimalsConverterHelper.sol";
import {MAX_ALLOWED_NATIVE_TOKEN_DECIMALS} from "../libraries/Constants.sol";

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

/**
* @title Staging ground for incoming and outgoing messages
Expand All @@ -20,19 +29,37 @@ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
* - The token must only be transferrable via a call to the token address itself
* - The token must only be able to set allowance via a call to the token address itself
* - The token must not have a callback on transfer, and more generally a user must not be able to make a transfer to themselves revert
* - The token must have a max of 2^256 - 1 wei total supply unscaled
* - The token must have a max of 2^256 - 1 wei total supply when scaled to 18 decimals
*/
contract ERC20Bridge is AbsBridge, IERC20Bridge {
using SafeERC20 for IERC20;

/// @inheritdoc IERC20Bridge
address public nativeToken;

/// @inheritdoc IERC20Bridge
uint8 public nativeTokenDecimals;

/// @inheritdoc IERC20Bridge
function initialize(IOwnable rollup_, address nativeToken_) external initializer onlyDelegated {
if (nativeToken_ == address(0)) revert InvalidTokenSet(nativeToken_);
nativeToken = nativeToken_;
_activeOutbox = EMPTY_ACTIVEOUTBOX;
rollup = rollup_;

// store number of decimals used by native token
try ERC20(nativeToken_).decimals() returns (uint8 decimals) {
if (decimals > MAX_ALLOWED_NATIVE_TOKEN_DECIMALS) {
revert NativeTokenDecimalsTooLarge(decimals);
}
nativeTokenDecimals = decimals;
} catch {
// decimal is not part of the ERC20 spec
// assume it have 0 decimals if it does not have decimals() method
// we do this to align with the token bridge behavior
nativeTokenDecimals = 0;
}
}

/// @inheritdoc IERC20Bridge
Expand Down
32 changes: 28 additions & 4 deletions src/bridge/ERC20Inbox.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,14 @@ import "./IERC20Inbox.sol";
import "./IERC20Bridge.sol";
import "../libraries/AddressAliasHelper.sol";
import {L1MessageType_ethDeposit} from "../libraries/MessageTypes.sol";
import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
import {AmountTooLarge} from "../libraries/Error.sol";
import {MAX_UPSCALE_AMOUNT} from "../libraries/Constants.sol";

import {DecimalsConverterHelper} from "../libraries/DecimalsConverterHelper.sol";

import {AddressUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

/**
* @title Inbox for user and contract originated messages
Expand All @@ -33,7 +38,7 @@ contract ERC20Inbox is AbsInbox, IERC20Inbox {

// inbox holds native token in transit used to pay for retryable tickets, approve bridge to use it
address nativeToken = IERC20Bridge(address(bridge)).nativeToken();
IERC20(nativeToken).approve(address(bridge), type(uint256).max);
IERC20(nativeToken).safeApprove(address(bridge), type(uint256).max);
}

/// @inheritdoc IERC20Inbox
Expand All @@ -46,11 +51,12 @@ contract ERC20Inbox is AbsInbox, IERC20Inbox {
dest = AddressAliasHelper.applyL1ToL2Alias(msg.sender);
}

uint256 amountToMintOnL2 = _fromNativeTo18Decimals(amount);
return
_deliverMessage(
L1MessageType_ethDeposit,
msg.sender,
abi.encodePacked(dest, amount),
abi.encodePacked(dest, amountToMintOnL2),
amount
);
}
Expand Down Expand Up @@ -141,4 +147,22 @@ contract ERC20Inbox is AbsInbox, IERC20Inbox {
tokenAmount
);
}

/// @inheritdoc AbsInbox
function _fromNativeTo18Decimals(uint256 value) internal view override returns (uint256) {
// In order to keep compatibility of child chain's native currency with external 3rd party tooling we
// expect 18 decimals to be always used for native currency. If native token uses different number of
// decimals then here it will be normalized to 18. Keep in mind, when withdrawing from child chain back
// to parent chain then the amount has to match native token's granularity, otherwise it will be rounded
// down.
uint8 nativeTokenDecimals = IERC20Bridge(address(bridge)).nativeTokenDecimals();

// Also make sure that inflated amount does not overflow uint256
if (nativeTokenDecimals < 18) {
if (value > MAX_UPSCALE_AMOUNT) {
revert AmountTooLarge(value);
}
}
return DecimalsConverterHelper.adjustDecimals(value, nativeTokenDecimals, 18);
}
}
Loading

0 comments on commit 5cf93d2

Please sign in to comment.