Skip to content

Commit

Permalink
feat: Add support for non-18 decimals token
Browse files Browse the repository at this point in the history
  • Loading branch information
chrstph-dvx committed Oct 16, 2024
1 parent 5b9a587 commit d05d7b1
Show file tree
Hide file tree
Showing 14 changed files with 303 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,47 @@ exports[`creates config for a chain on top of base with defaults 1`] = `
"wasmModuleRoot": "0x184884e1eb9fefdc158f6c8ac912bb183bf3cf83f0090317e0bc4ac5860baa39",
}
`;

exports[`creates config for a chain with non-18 decimals token on top of arbitrum sepolia with defaults 1`] = `
{
"baseStake": 100000n,
"chainConfig": "{\\"homesteadBlock\\":0,\\"daoForkBlock\\":null,\\"daoForkSupport\\":true,\\"eip150Block\\":0,\\"eip150Hash\\":\\"0x0000000000000000000000000000000000000000000000000000000000000000\\",\\"eip155Block\\":0,\\"eip158Block\\":0,\\"byzantiumBlock\\":0,\\"constantinopleBlock\\":0,\\"petersburgBlock\\":0,\\"istanbulBlock\\":0,\\"muirGlacierBlock\\":0,\\"berlinBlock\\":0,\\"londonBlock\\":0,\\"clique\\":{\\"period\\":0,\\"epoch\\":0},\\"arbitrum\\":{\\"EnableArbOS\\":true,\\"AllowDebugPrecompiles\\":false,\\"DataAvailabilityCommittee\\":false,\\"InitialArbOSVersion\\":32,\\"GenesisBlockNum\\":0,\\"MaxCodeSize\\":24576,\\"MaxInitCodeSize\\":49152,\\"InitialChainOwner\\":\\"0xd8da6bf26964af9d7eed9e03e53415d37aa96045\\"},\\"chainId\\":69420}",
"chainId": 69420n,
"confirmPeriodBlocks": 150n,
"decimals": 6,
"extraChallengeTimeBlocks": 0n,
"genesisBlockNum": 0n,
"loserStakeEscrow": "0x0000000000000000000000000000000000000000",
"owner": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
"sequencerInboxMaxTimeVariation": {
"delayBlocks": 28800n,
"delaySeconds": 345600n,
"futureBlocks": 300n,
"futureSeconds": 3600n,
},
"stakeToken": "0x0000000000000000000000000000000000000000",
"wasmModuleRoot": "0x184884e1eb9fefdc158f6c8ac912bb183bf3cf83f0090317e0bc4ac5860baa39",
}
`;

exports[`creates config for a chain with non-18 decimals token on top of arbitrum sepolia with overrides 1`] = `
{
"baseStake": 100000n,
"chainConfig": "{\\"homesteadBlock\\":0,\\"daoForkBlock\\":null,\\"daoForkSupport\\":true,\\"eip150Block\\":0,\\"eip150Hash\\":\\"0x0000000000000000000000000000000000000000000000000000000000000000\\",\\"eip155Block\\":0,\\"eip158Block\\":0,\\"byzantiumBlock\\":0,\\"constantinopleBlock\\":0,\\"petersburgBlock\\":0,\\"istanbulBlock\\":0,\\"muirGlacierBlock\\":0,\\"berlinBlock\\":0,\\"londonBlock\\":0,\\"clique\\":{\\"period\\":0,\\"epoch\\":0},\\"arbitrum\\":{\\"EnableArbOS\\":true,\\"AllowDebugPrecompiles\\":false,\\"DataAvailabilityCommittee\\":true,\\"InitialArbOSVersion\\":20,\\"GenesisBlockNum\\":0,\\"MaxCodeSize\\":24576,\\"MaxInitCodeSize\\":49152,\\"InitialChainOwner\\":\\"0xd8da6bf26964af9d7eed9e03e53415d37aa96045\\"},\\"chainId\\":69420}",
"chainId": 69420n,
"confirmPeriodBlocks": 4200n,
"decimals": 6,
"extraChallengeTimeBlocks": 5n,
"genesisBlockNum": 0n,
"loserStakeEscrow": "0x0000000000000000000000000000000000000001",
"owner": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
"sequencerInboxMaxTimeVariation": {
"delayBlocks": 200n,
"delaySeconds": 5n,
"futureBlocks": 100n,
"futureSeconds": 1n,
},
"stakeToken": "0x0000000000000000000000000000000000000002",
"wasmModuleRoot": "0xWasmModuleRoot",
}
`;
13 changes: 2 additions & 11 deletions 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 Expand Up @@ -68,7 +68,7 @@ describe(`create an AnyTrust chain that uses a custom gas token`, async () => {
client: parentChainPublicClient,
});

it(`successfully deploys core contracts through rollup creator`, async () => {
it(`successfully deploys core contracts through rollup creator (18 decimals)`, async () => {
// assert all inputs are correct
const [arg] = createRollupInformation.transaction.getInputs();
expect(arg.config).toEqual(createRollupConfig);
Expand All @@ -85,13 +85,4 @@ describe(`create an AnyTrust chain that uses a custom gas token`, async () => {
// assert the core contracts were successfully obtained
expect(createRollupInformation.coreContracts).toBeDefined();
});

it(`finds the transaction hash that created a specified deployed rollup contract`, async () => {
const transactionHash = await createRollupFetchTransactionHash({
rollup: createRollupInformation.coreContracts.rollup,
publicClient: parentChainPublicClient,
});

expect(transactionHash).toEqual(createRollupInformation.transactionReceipt.transactionHash);
});
});
11 changes: 10 additions & 1 deletion src/createRollupGetRetryablesFees.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
EstimateGasParameters,
encodeFunctionData,
decodeFunctionResult,
zeroAddress,
} from 'viem';

import { rollupCreatorABI } from './contracts/RollupCreator';
Expand All @@ -15,6 +16,7 @@ import { isCustomFeeTokenAddress } from './utils/isCustomFeeTokenAddress';
import { defaults as createRollupDefaults } from './createRollupDefaults';
import { applyPercentIncrease } from './utils/gasOverrides';
import { createRollupDefaultRetryablesFees } from './constants';
import { getNativeTokenDecimals, scaleToNativeTokenDecimals } from './utils/decimals';

const deployHelperABI = [
{
Expand Down Expand Up @@ -100,7 +102,14 @@ export async function createRollupGetRetryablesFees<TChain extends Chain | undef
const isCustomGasToken = isCustomFeeTokenAddress(nativeToken);

const inbox = isCustomGasToken ? erc20TemplateInbox : ethTemplateInbox;
const maxFeePerGas = maxFeePerGasForRetryables ?? createRollupDefaults.maxFeePerGasForRetryables;
const decimals = await getNativeTokenDecimals({
publicClient,
nativeTokenAddress: nativeToken ?? zeroAddress,
});
const maxFeePerGas = scaleToNativeTokenDecimals({
amount: maxFeePerGasForRetryables ?? createRollupDefaults.maxFeePerGasForRetryables,
decimals,
});

const baseFeeWithBuffer = applyPercentIncrease({
base: await publicClient.getGasPrice(),
Expand Down
13 changes: 11 additions & 2 deletions src/createRollupGetRetryablesFees.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,22 @@ it('successfully fetches retryable fees for an eth-based chain', async () => {
expect(fees).toBeGreaterThanOrEqual(124708400000000000n);
});

it('successfully fetches retryable fees for a custom gas token chain', async () => {
it.only('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);

const usdcFees = await createRollupGetRetryablesFees(sepoliaClient, {
account: '0x38f918D0E9F1b721EDaA41302E399fa1B79333a9',
nativeToken: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
maxFeePerGasForRetryables: parseGwei('0.1'),
});

expect(usdcFees).toBeTypeOf('bigint');
expect(usdcFees).toEqual(124708400000000000n);
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { getRollupCreatorAddress } from './utils/getRollupCreatorAddress';
import { Prettify } from './types/utils';
import { WithRollupCreatorAddressOverride } from './types/createRollupTypes';
import { createRollupGetRetryablesFeesWithDefaults } from './createRollupGetRetryablesFees';
import { applyPercentIncrease } from './utils/gasOverrides';

export type CreateRollupPrepareCustomFeeTokenApprovalTransactionRequestParams<
TChain extends Chain | undefined,
Expand Down
14 changes: 13 additions & 1 deletion src/createRollupPrepareDeploymentParamsConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
SequencerInboxMaxTimeVariation,
getDefaultSequencerInboxMaxTimeVariation,
} from './getDefaultSequencerInboxMaxTimeVariation';
import { scaleToNativeTokenDecimals } from './utils/decimals';

export type CreateRollupPrepareDeploymentParamsConfigResult =
CreateRollupFunctionInputs[0]['config'];
Expand All @@ -22,7 +23,9 @@ type RequiredKeys = 'chainId' | 'owner';
type RequiredParams = Pick<CreateRollupPrepareDeploymentParamsConfigResult, RequiredKeys>;
type OptionalParams = Partial<
Omit<CreateRollupPrepareDeploymentParamsConfigResult, 'chainConfig' | RequiredKeys>
>;
> & {
decimals?: number;
};

export type CreateRollupPrepareDeploymentParamsConfigParams = Prettify<
RequiredParams & { chainConfig?: ChainConfig } & OptionalParams
Expand Down Expand Up @@ -50,6 +53,7 @@ export type CreateRollupPrepareDeploymentParamsConfigParams = Prettify<
* @param {BigInt} [params.sequencerInboxMaxTimeVariation.futureBlocks]
* @param {BigInt} [params.sequencerInboxMaxTimeVariation.delaySeconds]
* @param {BigInt} [params.sequencerInboxMaxTimeVariation.futureSeconds]
* @param {number} [params.decimals]
*
* @returns {Object} {@link CreateRollupPrepareDeploymentParamsConfigResult}
*
Expand Down Expand Up @@ -109,10 +113,18 @@ export function createRollupPrepareDeploymentParamsConfig<TChain extends Chain |
};
}

// Non-18 decimals, scale baseStake
let baseStake = params.baseStake ?? defaults.baseStake;
const decimals = params.decimals;
if (typeof decimals === 'number') {
baseStake = scaleToNativeTokenDecimals({ amount: baseStake, decimals });
}

return {
...defaults,
...paramsByParentBlockTime,
...params,
baseStake,
chainConfig: JSON.stringify(
chainConfig ??
prepareChainConfig({
Expand Down
29 changes: 29 additions & 0 deletions src/createRollupPrepareDeploymentParamsConfig.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,3 +192,32 @@ it('creates a config for a chain on top of a custom parent chain', () => {
}),
).toMatchSnapshot();
});

it('creates config for a chain with non-18 decimals token on top of arbitrum sepolia with defaults', async () => {
const arbitrumSepoliaClient = createPublicClient({
chain: arbitrumSepolia,
transport: http(),
});

expect(
createRollupPrepareDeploymentParamsConfig(arbitrumSepoliaClient, {
owner: vitalik,
chainId,
decimals: 6,
}),
).toMatchSnapshot();
});

it('creates config for a chain with non-18 decimals token on top of arbitrum sepolia with overrides', () => {
const arbitrumSepoliaClient = createPublicClient({
chain: arbitrumSepolia,
transport: http(),
});

expect(
createRollupPrepareDeploymentParamsConfig(arbitrumSepoliaClient, {
...getOverrides({ owner: vitalik, chainId }),
decimals: 6,
}),
).toMatchSnapshot();
});
19 changes: 12 additions & 7 deletions src/createRollupPrepareTransactionRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
WithRollupCreatorAddressOverride,
} from './types/createRollupTypes';
import { isKnownWasmModuleRoot, getConsensusReleaseByWasmModuleRoot } from './wasmModuleRoot';
import { getNativeTokenDecimals, scaleToNativeTokenDecimals } from './utils/decimals';

function createRollupEncodeFunctionData(args: CreateRollupFunctionInputs) {
return encodeFunctionData({
Expand Down Expand Up @@ -67,13 +68,6 @@ export async function createRollupPrepareTransactionRequest<TChain extends Chain
`"params.nativeToken" can only be used on AnyTrust chains. Set "arbitrum.DataAvailabilityCommittee" to "true" in the chain config.`,
);
}

// custom fee token is only allowed to have 18 decimals
if ((await fetchDecimals({ address: params.nativeToken, publicClient })) !== 18) {
throw new Error(
`"params.nativeToken" can only be configured with a token that uses 18 decimals.`,
);
}
}

let maxDataSize: bigint;
Expand Down Expand Up @@ -109,6 +103,17 @@ export async function createRollupPrepareTransactionRequest<TChain extends Chain

const batchPosterManager = params.batchPosterManager ?? zeroAddress;
const paramsWithDefaults = { ...defaults, ...params, maxDataSize, batchPosterManager };
if (params.nativeToken) {
const decimals = await getNativeTokenDecimals({
publicClient,
nativeTokenAddress: params.nativeToken,
});
paramsWithDefaults.maxFeePerGasForRetryables = scaleToNativeTokenDecimals({
amount: paramsWithDefaults.maxFeePerGasForRetryables,
decimals,
});
}

const createRollupGetCallValueParams = { ...paramsWithDefaults, account };

// @ts-ignore (todo: fix viem type issue)
Expand Down
67 changes: 35 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,38 @@ 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" 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 },
});

const txRequest = await createRollupPrepareTransactionRequest({
params: {
config: createRollupPrepareDeploymentParamsConfig(publicClient, {
chainId: BigInt(chainId),
owner: deployer.address,
chainConfig,
}),
batchPosters: [deployer.address],
validators: [deployer.address],
nativeToken: '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d',
},
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);
});
6 changes: 5 additions & 1 deletion src/createTokenBridge.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ describe('createTokenBridge', () => {
checkWethGateways(tokenBridgeContracts, { customFeeToken: false });
});

it('successfully deploys token bridge contracts with a custom fee token', async () => {
it('successfully deploys token bridge contracts with a custom fee token (18 decimals)', async () => {
const testnodeInformation = getInformationFromTestnode();

// deploy a fresh token bridge creator, because it is only possible to deploy one token bridge per rollup per token bridge creator
Expand Down Expand Up @@ -437,6 +437,10 @@ describe('createTokenBridge', () => {
checkWethGateways(tokenBridgeContracts, { customFeeToken: true });
});

it('successfully deploys token bridge contracts with a custom fee token (non-18 decimals)', async () => {
// Deploy a custom chain with non-18 decimals
});

it('should throw when createTokenBridge is called multiple times', async () => {
const testnodeInformation = getInformationFromTestnode();

Expand Down
7 changes: 6 additions & 1 deletion src/createTokenBridgeEnoughCustomFeeTokenAllowance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { createTokenBridgeDefaultRetryablesFees } from './constants';
import { Prettify } from './types/utils';
import { WithTokenBridgeCreatorAddressOverride } from './types/createTokenBridgeTypes';
import { getTokenBridgeCreatorAddress } from './utils/getTokenBridgeCreatorAddress';
import { getNativeTokenDecimals, scaleToNativeTokenDecimals } from './utils/decimals';

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

return allowance >= createTokenBridgeDefaultRetryablesFees;
const decimals = await getNativeTokenDecimals({ publicClient, nativeTokenAddress: nativeToken });
return (
allowance >=
scaleToNativeTokenDecimals({ amount: createTokenBridgeDefaultRetryablesFees, decimals })
);
}
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
Loading

0 comments on commit d05d7b1

Please sign in to comment.