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

feat: add support for non-18 decimal custom gas token #216

Merged
merged 26 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
b34bf64
feat: Add support for non-18 decimals token
chrstph-dvx Oct 16, 2024
e68dea8
Cleanup
chrstph-dvx Oct 16, 2024
9d6ad70
Bump arbitrum sdk
chrstph-dvx Oct 23, 2024
2b66ced
Run tests for non-18 decimals
chrstph-dvx Oct 23, 2024
36753af
Update workflow
chrstph-dvx Oct 23, 2024
d9d5ff5
Fix typing with decimals
chrstph-dvx Oct 23, 2024
5fbcccb
Revert baseStake change
chrstph-dvx Oct 23, 2024
d7b49e8
Add isTestnet to registerNewNetwork
chrstph-dvx Oct 23, 2024
dd70e96
Cleanup
chrstph-dvx Oct 23, 2024
ba60587
Update createRollupPrepareTransactionRequest
chrstph-dvx Oct 23, 2024
afb18de
Remove tests
chrstph-dvx Oct 24, 2024
761ecdb
Remove unused imports
chrstph-dvx Oct 24, 2024
b85bce6
Add back condition for decimals over 36
chrstph-dvx Oct 24, 2024
ca7d835
Move nitro-testnode chains to testnets array
chrstph-dvx Oct 24, 2024
d3a4c16
update
spsjvc Oct 24, 2024
05a033e
Review comments
chrstph-dvx Oct 24, 2024
5feb267
scale everything
spsjvc Oct 24, 2024
3115b0f
Merge branch 'fs-899-support-non-18-decimals-custom-gas-token' of git…
spsjvc Oct 24, 2024
bc8a662
remove redundant test
chrstph-dvx Oct 24, 2024
a1a33d8
label
spsjvc Oct 24, 2024
2d18752
Merge branch 'fs-899-support-non-18-decimals-custom-gas-token' of git…
spsjvc Oct 24, 2024
1609ccd
Update decimals for nitro testnode
chrstph-dvx Oct 24, 2024
0cba47c
fix
spsjvc Oct 24, 2024
9e0d20f
Merge branch 'fs-899-support-non-18-decimals-custom-gas-token' of git…
spsjvc Oct 24, 2024
0072677
fix
spsjvc Oct 24, 2024
79daf09
fix
spsjvc Oct 24, 2024
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
15 changes: 12 additions & 3 deletions .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,17 @@ jobs:
run: yarn test:unit

test-integration:
name: Test (Integration)
name: Test (Integration) - ${{ matrix.config.name }}
runs-on: ubuntu-latest
strategy:
matrix:
config:
- name: Custom gas token with 18 decimals
args: --tokenbridge --l3node --l3-token-bridge --l3-fee-token
decimals: 18
- name: Custom gas token with 6 decimals
args: --tokenbridge --l3node --l3-token-bridge --l3-fee-token --l3-fee-token-decimals 6
decimals: 6
steps:
- name: Checkout
uses: actions/checkout@v4
Expand All @@ -72,7 +81,7 @@ jobs:
uses: OffchainLabs/actions/run-nitro-test-node@feat-simplify
with:
nitro-testnode-ref: release
args: --tokenbridge --l3node --l3-token-bridge --l3-fee-token
args: ${{ matrix.config.args }}

- name: Copy .env
run: cp ./.env.example ./.env
Expand All @@ -81,4 +90,4 @@ jobs:
run: yarn build

- name: Test
run: yarn test:integration
run: DECIMALS=${{matrix.config.decimals}} yarn test:integration
9 changes: 7 additions & 2 deletions src/chains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,13 +125,16 @@ export function registerCustomParentChain(
customParentChains[chain.id] = chain;
}

export const chains = [
export const mainnets = [
// mainnet L1
mainnet,
// mainnet L2
arbitrumOne,
arbitrumNova,
base,
];

export const testnets = [
// testnet L1
sepolia,
holesky,
Expand All @@ -142,7 +145,9 @@ export const chains = [
nitroTestnodeL1,
nitroTestnodeL2,
nitroTestnodeL3,
] as const;
];

export const chains = [...mainnets, ...testnets] as const;

export {
// mainnet L1
Expand Down
2 changes: 1 addition & 1 deletion src/createRollup.integration.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest';
import { Address, createPublicClient, http, parseGwei, zeroAddress } from 'viem';
import { createPublicClient, http, parseGwei, zeroAddress } from 'viem';

import { nitroTestnodeL2 } from './chains';
import {
Expand Down
10 changes: 8 additions & 2 deletions src/createRollupEnoughCustomFeeTokenAllowance.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Address, PublicClient, Transport, Chain } from 'viem';

import { fetchAllowance } from './utils/erc20';
import { fetchAllowance, fetchDecimals } from './utils/erc20';
import { getRollupCreatorAddress } from './utils/getRollupCreatorAddress';

import { Prettify } from './types/utils';
import { WithRollupCreatorAddressOverride } from './types/createRollupTypes';
import { createRollupGetRetryablesFeesWithDefaults } from './createRollupGetRetryablesFees';
import { scaleToNativeTokenDecimals } from './utils/decimals';

export type CreateRollupEnoughCustomFeeTokenAllowanceParams<TChain extends Chain | undefined> =
Prettify<
Expand Down Expand Up @@ -37,5 +38,10 @@ export async function createRollupEnoughCustomFeeTokenAllowance<TChain extends C
maxFeePerGasForRetryables,
});

return allowance >= fees;
const decimals = await fetchDecimals({
address: nativeToken,
publicClient,
});

return allowance >= scaleToNativeTokenDecimals({ amount: fees, decimals });
}
16 changes: 11 additions & 5 deletions src/createRollupGetRetryablesFees.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
EstimateGasParameters,
encodeFunctionData,
decodeFunctionResult,
parseEther,
} from 'viem';

import { rollupCreatorABI } from './contracts/RollupCreator';
Expand Down Expand Up @@ -104,9 +105,8 @@ export async function createRollupGetRetryablesFees<TChain extends Chain | undef

const baseFeeWithBuffer = applyPercentIncrease({
base: await publicClient.getGasPrice(),
// for custom gas token chains, retryable fees don't scale with parent base fee, so there's no need for any buffer
// for eth chains, add 30% buffer in case of a spike
percentIncrease: isCustomGasToken ? undefined : 30n,
// add 30% buffer in case of a spike
percentIncrease: 30n,
});

const callParams: CallParameters = {
Expand Down Expand Up @@ -140,8 +140,14 @@ export async function createRollupGetRetryablesFees<TChain extends Chain | undef
});

return isCustomGasToken
? // for custom gas token chains, retryable fees don't scale with parent base fee, so there's no need for any buffer
decodedResult
? // for custom gas token chains, retryable fees don't scale with parent base fee and are constant at 124708400000000000
//
// we add some buffer (around 100k gwei) due to potential rounding issues for non-18 decimals, because:
// - in the sdk, we get the total cost, then scale and round up
// - in the contract, we scale and round up each component, then add them together, which can lead to a very tiny discrepancy
//
// https://github.com/OffchainLabs/nitro-contracts/blob/main/src/rollup/RollupCreator.sol#L287-L302
parseEther('0.1248')
: // for eth chains, add 3% buffer
applyPercentIncrease({ base: decodedResult, percentIncrease: 3n });
}
Expand Down
4 changes: 2 additions & 2 deletions src/createRollupGetRetryablesFees.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ it('successfully fetches retryable fees for an eth-based chain', async () => {
it('successfully fetches retryable fees for a custom gas token chain', async () => {
const fees = await createRollupGetRetryablesFees(sepoliaClient, {
account: '0x38f918D0E9F1b721EDaA41302E399fa1B79333a9',
nativeToken: '0xaf88d065e77c8cc2239327c5edb3a432268e5831',
nativeToken: '0x0625afb445c3b6b7b929342a04a22599fd5dbb59',
maxFeePerGasForRetryables: parseGwei('0.1'),
});

expect(fees).toBeTypeOf('bigint');
expect(fees).toEqual(124708400000000000n);
expect(fees).toEqual(124800000000000000n);
});
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Address, PublicClient, Transport, Chain } from 'viem';

import { approvePrepareTransactionRequest } from './utils/erc20';
import { approvePrepareTransactionRequest, fetchDecimals } from './utils/erc20';
import { validateParentChain } from './types/ParentChain';
import { getRollupCreatorAddress } from './utils/getRollupCreatorAddress';

import { Prettify } from './types/utils';
import { WithRollupCreatorAddressOverride } from './types/createRollupTypes';
import { createRollupGetRetryablesFeesWithDefaults } from './createRollupGetRetryablesFees';
import { applyPercentIncrease } from './utils/gasOverrides';
import { scaleToNativeTokenDecimals } from './utils/decimals';

export type CreateRollupPrepareCustomFeeTokenApprovalTransactionRequestParams<
TChain extends Chain | undefined,
Expand Down Expand Up @@ -39,11 +39,16 @@ export async function createRollupPrepareCustomFeeTokenApprovalTransactionReques
maxFeePerGasForRetryables,
});

const decimals = await fetchDecimals({
address: nativeToken,
publicClient,
});

const request = await approvePrepareTransactionRequest({
address: nativeToken,
owner: account,
spender: rollupCreatorAddressOverride ?? getRollupCreatorAddress(publicClient),
amount: amount ?? fees,
amount: amount ?? scaleToNativeTokenDecimals({ amount: fees, decimals }),
publicClient,
});

Expand Down
5 changes: 2 additions & 3 deletions src/createRollupPrepareTransactionRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,9 @@ export async function createRollupPrepareTransactionRequest<TChain extends Chain
);
}

// custom fee token is only allowed to have 18 decimals
if ((await fetchDecimals({ address: params.nativeToken, publicClient })) !== 18) {
if ((await fetchDecimals({ address: params.nativeToken, publicClient })) > 36) {
throw new Error(
`"params.nativeToken" can only be configured with a token that uses 18 decimals.`,
`"params.nativeToken" can only be configured with a token that uses 36 decimals or less.`,
);
}
}
Expand Down
66 changes: 34 additions & 32 deletions src/createRollupPrepareTransactionRequest.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,38 +262,6 @@ it(`fails to prepare transaction request if ArbOS version is incompatible with C
);
});

it(`fails to prepare transaction request if "params.nativeToken" doesn't use 18 decimals`, async () => {
// generate a random chain id
const chainId = generateChainId();

// create the chain config
const chainConfig = prepareChainConfig({
chainId,
arbitrum: { InitialChainOwner: deployer.address, DataAvailabilityCommittee: true },
});

// prepare the transaction for deploying the core contracts
await expect(
createRollupPrepareTransactionRequest({
params: {
config: createRollupPrepareDeploymentParamsConfig(publicClient, {
chainId: BigInt(chainId),
owner: deployer.address,
chainConfig,
}),
batchPosters: [deployer.address],
validators: [deployer.address],
// USDC on Arbitrum Sepolia has 6 decimals
nativeToken: '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d',
},
account: deployer.address,
publicClient,
}),
).rejects.toThrowError(
`"params.nativeToken" can only be configured with a token that uses 18 decimals.`,
);
});

it(`fails to prepare transaction request if "params.maxDataSize" is not provided for a custom parent chain`, async () => {
// generate a random chain id
const chainId = generateChainId();
Expand Down Expand Up @@ -458,3 +426,37 @@ it(`successfully prepares a transaction request with a custom parent chain`, asy
expect(txRequest.chainId).toEqual(chainId);
expect(txRequest.gas).toEqual(1_000n);
});

it(`successfully prepare transaction request if "params.nativeToken" uses supported number of decimals`, async () => {
// generate a random chain id
const chainId = generateChainId();

// create the chain config
const chainConfig = prepareChainConfig({
chainId,
arbitrum: { InitialChainOwner: deployer.address, DataAvailabilityCommittee: true },
});

const txRequest = await createRollupPrepareTransactionRequest({
params: {
config: createRollupPrepareDeploymentParamsConfig(publicClient, {
chainId: BigInt(chainId),
owner: deployer.address,
chainConfig,
}),
batchPosters: [deployer.address],
validators: [deployer.address],
nativeToken: '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d', // USDC
},
value: createRollupDefaultRetryablesFees,
account: deployer.address,
publicClient,
gasOverrides: { gasLimit: { base: 1_000n } },
});

expect(txRequest.account).toEqual(deployer.address);
expect(txRequest.from).toEqual(deployer.address);
expect(txRequest.to).toEqual(rollupCreatorAddress[arbitrumSepolia.id]);
expect(txRequest.chainId).toEqual(arbitrumSepolia.id);
expect(txRequest.gas).toEqual(1_000n);
});
13 changes: 11 additions & 2 deletions src/createTokenBridge.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { createTokenBridgePrepareSetWethGatewayTransactionRequest } from './crea
import { createTokenBridgePrepareSetWethGatewayTransactionReceipt } from './createTokenBridgePrepareSetWethGatewayTransactionReceipt';
import { createTokenBridge } from './createTokenBridge';
import { TokenBridgeContracts } from './types/TokenBridgeContracts';
import { scaleToNativeTokenDecimals } from './utils/decimals';

const testnodeAccounts = getNitroTestnodePrivateKeyAccounts();
const l2RollupOwner = testnodeAccounts.l2RollupOwner;
Expand Down Expand Up @@ -104,6 +105,8 @@ async function checkWethGateways(
expect(tokenBridgeContracts.orbitChainContracts.wethGateway).not.toEqual(zeroAddress);
}

const nativeTokenDecimals = process.env.DECIMALS ? Number(process.env.DECIMALS) : 18;

describe('createTokenBridge utils function', () => {
it(`successfully deploys token bridge contracts through token bridge creator`, async () => {
const testnodeInformation = getInformationFromTestnode();
Expand Down Expand Up @@ -218,7 +221,10 @@ describe('createTokenBridge utils function', () => {
data: encodeFunctionData({
abi: erc20ABI,
functionName: 'transfer',
args: [l3RollupOwner.address, parseEther('500')],
args: [
l3RollupOwner.address,
scaleToNativeTokenDecimals({ amount: 500n, decimals: nativeTokenDecimals }),
],
}),
value: BigInt(0),
account: l3TokenBridgeDeployer,
Expand Down Expand Up @@ -384,7 +390,10 @@ describe('createTokenBridge', () => {
data: encodeFunctionData({
abi: erc20ABI,
functionName: 'transfer',
args: [l3RollupOwner.address, parseEther('500')],
args: [
l3RollupOwner.address,
scaleToNativeTokenDecimals({ amount: 500n, decimals: nativeTokenDecimals }),
],
}),
value: BigInt(0),
account: l3TokenBridgeDeployer,
Expand Down
16 changes: 14 additions & 2 deletions src/createTokenBridgeEnoughCustomFeeTokenAllowance.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Address, PublicClient, Transport, Chain } from 'viem';

import { fetchAllowance } from './utils/erc20';
import { fetchAllowance, fetchDecimals } from './utils/erc20';
import { createTokenBridgeDefaultRetryablesFees } from './constants';

import { Prettify } from './types/utils';
import { WithTokenBridgeCreatorAddressOverride } from './types/createTokenBridgeTypes';
import { getTokenBridgeCreatorAddress } from './utils/getTokenBridgeCreatorAddress';
import { scaleToNativeTokenDecimals } from './utils/decimals';

export type CreateTokenBridgeEnoughCustomFeeTokenAllowanceParams<TChain extends Chain | undefined> =
Prettify<
Expand All @@ -31,5 +32,16 @@ export async function createTokenBridgeEnoughCustomFeeTokenAllowance<
publicClient,
});

return allowance >= createTokenBridgeDefaultRetryablesFees;
const decimals = await fetchDecimals({
address: nativeToken,
publicClient,
});

return (
allowance >=
scaleToNativeTokenDecimals({
amount: createTokenBridgeDefaultRetryablesFees,
decimals,
})
);
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { Address, PublicClient, Transport, Chain, maxInt256 } from 'viem';

import { approvePrepareTransactionRequest } from './utils/erc20';
import { approvePrepareTransactionRequest, fetchDecimals } from './utils/erc20';

import { Prettify } from './types/utils';
import { validateParentChain } from './types/ParentChain';
import { WithTokenBridgeCreatorAddressOverride } from './types/createTokenBridgeTypes';
import { getTokenBridgeCreatorAddress } from './utils/getTokenBridgeCreatorAddress';
import { createTokenBridgeDefaultRetryablesFees } from './constants';
import { scaleToNativeTokenDecimals } from './utils/decimals';

export type CreateTokenBridgePrepareCustomFeeTokenApprovalTransactionRequestParams<
TChain extends Chain | undefined,
Expand All @@ -30,11 +31,21 @@ export async function createTokenBridgePrepareCustomFeeTokenApprovalTransactionR
}: CreateTokenBridgePrepareCustomFeeTokenApprovalTransactionRequestParams<TChain>) {
const { chainId } = validateParentChain(publicClient);

const decimals = await fetchDecimals({
address: nativeToken,
publicClient,
});

const request = await approvePrepareTransactionRequest({
address: nativeToken,
owner,
spender: tokenBridgeCreatorAddressOverride ?? getTokenBridgeCreatorAddress(publicClient),
amount: amount ?? createTokenBridgeDefaultRetryablesFees,
amount:
amount ??
scaleToNativeTokenDecimals({
amount: createTokenBridgeDefaultRetryablesFees,
decimals,
}),
publicClient,
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { it, expect } from 'vitest';
import { Address, createPublicClient, http, parseGwei, Client } from 'viem';
import { Address, createPublicClient, http } from 'viem';
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';
import { nitroTestnodeL3 } from '../chains';
import { arbOwnerPublicActions } from './arbOwnerPublicActions';
Expand Down
2 changes: 1 addition & 1 deletion src/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"viem": "^1.20.0"
},
"dependencies": {
"@arbitrum/sdk": "^4.0.0",
"@arbitrum/sdk": "^4.0.2-beta.0",
"@arbitrum/token-bridge-contracts": "^1.2.2",
"@offchainlabs/fund-distribution-contracts": "^1.0.1",
"@safe-global/protocol-kit": "^4.0.2",
Expand Down
Loading