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

chore(protocol): improve test coverage #16428

Merged
merged 16 commits into from
Mar 19, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@
<span class="title-subsection-bold text-base text-secondary-content">{$t('token_dropdown.label')}</span>
{:else if value}
<div class="flex f-space-between space-x-2 items-center text-secondary-content">
<!-- Only match icons to configurd tokens -->
<!-- Only match icons to configured tokens -->
dantaik marked this conversation as resolved.
Show resolved Hide resolved
{#if symbolToIconMap[value.symbol] && !value.imported}
<i role="img" aria-label={value.name}>
<svelte:component this={symbolToIconMap[value.symbol]} size={20} />
Expand Down
6 changes: 3 additions & 3 deletions packages/protocol/docs/native_token_support.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@

![Wrapped_vs_Native](./images/native_support.png "Wrapped vs. Native bridging")

Taiko's briding concept is a lock-and-mint type. It simply means (the red path above) on the canonical chain we take custody of the assets and on the destination chain we mint the wrapped counterpart. When someone wants to bridge back (from destination to canonical) it will first burn the tokens, then release the funds on the canonical chain.
Taiko's bridging concept is a lock-and-mint type. It simply means (the red path above) on the canonical chain we take custody of the assets and on the destination chain we mint the wrapped counterpart. When someone wants to bridge back (from destination to canonical) it will first burn the tokens, then release the funds on the canonical chain.

But there might be some incentives (e.g.: adoption, liquidity fragmentation, etc.) when deploying a native token on the destination chain is beneficial. For this reason Taiko introduced the possibility of deploying the canonical assets (together with all their sub/parent/proxy contracts) and plug it into our ERC20Vault via adapters (green path).

Important to note that while wrapped asset briding is 'automatical', the native one requires the willingness and efforts from Taiko side (and maybe also original token issuer green light to recognise officially as "native"), to support that type of asset-transfer.
Important to note that while wrapped asset bridging is 'automatical', the native one requires the willingness and efforts from Taiko side (and maybe also original token issuer green light to recognise officially as "native"), to support that type of asset-transfer.

## Howto

There are some steps to do in order to facilitate native token bridging. In the next steps, here is a TLDR breakdown how we do it with USDC.

1. Deploy the same (bytecode equivalent) ERC-20 token on L2. An example of the contracts + deployments can be found in our [USDC repo](https://github.com/taikoxyz/USDC).
2. Deploy adapter (e.g.: [USDC adapter](../contracts/tokenvault/adapters/USDCAdapter.sol)). As this will serve as the plug-in to our `ERC20Vault` for custom (native) tokens. This adapter serves multiple purposes. It is also a wrapper around the native token in a way - that it matches our conform `ERC20Vault` interfaces so that we can be sure any kind of native ERC-20 can be supported on L2. Also can handle custom logic required by the native asset (roles handling, specific logic, etc.).
3. Transfer the ownership (if not already owned by) to `ERC20Vault` owner since those 2 have to be owned by the same address. (!IMPORTANT! Not the token owned by the same owner, but the token adpter! USDC owner will still be Circle on L2.)
3. Transfer the ownership (if not already owned by) to `ERC20Vault` owner since those 2 have to be owned by the same address. (!IMPORTANT! Not the token owned by the same owner, but the token adapter! USDC owner will still be Circle on L2.)
4. Since our bridge is permissionless, there might have been some USDC bridge operations in the past. It would mean, there is already an existing `BridgedUSDC` on our L2. To overcome liquidity fragmentation, we (Taiko) need to call `ERC20Vault` `changeBridgedToken()` function with the appropriate parameters. This way the "old" `BridgedUSDC` can be migrated to this new native token and the bridging operation will mint into the new token frm that point on.

The above steps (2. - 4.) is incorporated into the script [DeployUSDCAdapter.s.sol](../script/DeployUSDCAdapter.s.sol).
103 changes: 103 additions & 0 deletions packages/protocol/test/L1/TaikoL1.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,13 @@ contract TaikoL1Test is TaikoL1TestBase {
vm.warp(block.timestamp + tierProvider().getTier(minTier).cooldownWindow * 60 + 1);

verifyBlock(Alice, 2);

(TaikoData.Block memory blk, TaikoData.TransitionState memory ts) = L1.getBlock(meta.id);
assertEq(meta.id, blk.blockId);

ts = L1.getTransition(meta.id, parentHash);
assertEq(ts.prover, Bob);

parentHash = blockHash;
}
printVariables("");
Expand Down Expand Up @@ -208,6 +215,10 @@ contract TaikoL1Test is TaikoL1TestBase {
giveEthAndTko(Henry, 0, maxAmount + 1 ether);

// So after this point we have 8 deposits

vm.prank(Alice, Alice);
bool canAliceDeposit = L1.canDepositEthToL2(1 ether);
assertEq(true, canAliceDeposit);
vm.prank(Alice, Alice);
L1.depositEtherToL2{ value: 1 ether }(address(0));
vm.prank(Bob, Bob);
Expand Down Expand Up @@ -246,4 +257,96 @@ contract TaikoL1Test is TaikoL1TestBase {
0x3b61cf81fd007398a8efd07a055ac8fb542bcfa62d76cf6dc28a889371afb21e
);
}

function test_pauseProving() external {
L1.pauseProving(true);

TaikoData.BlockMetadata memory meta;

giveEthAndTko(Alice, 1000 ether, 1000 ether);
giveEthAndTko(Bob, 1e8 ether, 100 ether);

// Proposing is still possible
(meta,) = proposeBlock(Alice, Bob, 1_000_000, 1024);
// Proving is not, so supply the revert reason to proveBlock
proveBlock(
Bob,
Bob,
meta,
GENESIS_BLOCK_HASH,
bytes32("01"),
bytes32("02"),
meta.minTier,
TaikoErrors.L1_PROVING_PAUSED.selector
);
}

function test_unpause() external {
L1.pause();

giveEthAndTko(Alice, 1000 ether, 1000 ether);
giveEthAndTko(Bob, 1e8 ether, 100 ether);

// Proposing is also not possible
proposeButRevert(Alice, Bob, 1024, EssentialContract.INVALID_PAUSE_STATUS.selector);

// unpause
L1.unpause();

// Proposing is possible again
proposeBlock(Alice, Bob, 1_000_000, 1024);
}

function test_burn() external {
uint256 balanceBeforeBurn = tko.balanceOf(address(this));
vm.prank(tko.owner(), tko.owner());
tko.burn(address(this), 1 ether);
uint256 balanceAfterBurn = tko.balanceOf(address(this));

assertEq(balanceBeforeBurn - 1 ether, balanceAfterBurn);
}

function test_snapshot() external {
vm.prank(tko.owner(), tko.owner());
tko.snapshot();

uint256 totalSupplyAtSnapshot = tko.totalSupplyAt(1);

vm.prank(tko.owner(), tko.owner());
tko.burn(address(this), 1 ether);

// At snapshot date vs. now, the total supply differs
assertEq(totalSupplyAtSnapshot, tko.totalSupply() + 1 ether);
}

function test_getTierIds() external {
uint16[] memory tiers = cp.getTierIds();
assertEq(tiers[0], LibTiers.TIER_OPTIMISTIC);
assertEq(tiers[1], LibTiers.TIER_SGX);
assertEq(tiers[2], LibTiers.TIER_GUARDIAN);

vm.expectRevert();
cp.getTier(123);
}

function proposeButRevert(
address proposer,
address prover,
uint24 txListSize,
bytes4 revertReason
)
internal
{
uint256 msgValue = 2 ether;
AssignmentHook.ProverAssignment memory assignment;
TaikoData.HookCall[] memory hookcalls = new TaikoData.HookCall[](1);
hookcalls[0] = TaikoData.HookCall(address(assignmentHook), abi.encode(assignment));

vm.prank(proposer, proposer);
vm.expectRevert(revertReason);
L1.proposeBlock{ value: msgValue }(
abi.encode(TaikoData.BlockParams(prover, address(0), 0, 0, hookcalls)),
new bytes(txListSize)
);
}
}
18 changes: 18 additions & 0 deletions packages/protocol/test/L2/TaikoL2.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ contract TestTaikoL2 is TaikoTest {

vm.roll(block.number + 1);
vm.warp(block.timestamp + 30);

vm.deal(address(L2), 100 ether);
}

function test_L2_AnchorTx_with_constant_block_time() external {
Expand Down Expand Up @@ -137,6 +139,22 @@ contract TestTaikoL2 is TaikoTest {
LibL2Signer.signAnchor(digest, uint8(3));
}

function test_L2_withdraw() external {
vm.prank(L2.owner(), L2.owner());
L2.withdraw(address(0), Alice);
assertEq(address(L2).balance, 0 ether);
assertEq(Alice.balance, 100 ether);

// Random EOA cannot call withdraw
vm.expectRevert();
vm.prank(Alice, Alice);
L2.withdraw(address(0), Alice);
}

function test_L2_getBlockHash() external {
assertEq(L2.getBlockHash(uint64(1000)), 0);
}

function _anchor(uint32 parentGasLimit) private {
bytes32 l1Hash = randBytes32();
bytes32 l1StateRoot = randBytes32();
Expand Down
64 changes: 64 additions & 0 deletions packages/protocol/test/bridge/Bridge.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,70 @@ contract BridgeTest is TaikoTest {
assertEq(status == IBridge.Status.DONE, true);
}

function test_Bridge_suspend_messages() public {
vm.startPrank(Alice);
(IBridge.Message memory message, bytes memory proof) =
setUpPredefinedSuccessfulProcessMessageCall();

bytes32 msgHash = destChainBridge.hashMessage(message);
bytes32[] memory messageHashes = new bytes32[](1);
messageHashes[0] = msgHash;

vm.stopPrank();
// Suspend
vm.prank(destChainBridge.owner(), destChainBridge.owner());
destChainBridge.suspendMessages(messageHashes, true);

vm.startPrank(Alice);
vm.expectRevert(Bridge.B_INVOCATION_TOO_EARLY.selector);
destChainBridge.processMessage(message, proof);

vm.stopPrank();
// Unsuspend
vm.prank(destChainBridge.owner(), destChainBridge.owner());
destChainBridge.suspendMessages(messageHashes, false);

vm.startPrank(Alice);
destChainBridge.processMessage(message, proof);

IBridge.Status status = destChainBridge.messageStatus(msgHash);

assertEq(status == IBridge.Status.DONE, true);
}

function test_Bridge_ban_address() public {
vm.startPrank(Alice);
(IBridge.Message memory message, bytes memory proof) =
setUpPredefinedSuccessfulProcessMessageCall();

bytes32 msgHash = destChainBridge.hashMessage(message);
bytes32[] memory messageHashes = new bytes32[](1);
messageHashes[0] = msgHash;

vm.stopPrank();
// Ban address
vm.prank(destChainBridge.owner(), destChainBridge.owner());
destChainBridge.banAddress(message.to, true);

vm.startPrank(Alice);
// processMessage() still marks it DONE but dont call the invokeMessageCall on them
destChainBridge.processMessage(message, proof);

IBridge.Status status = destChainBridge.messageStatus(msgHash);

assertEq(status == IBridge.Status.DONE, true);
}

function test_Bridge_prove_message_received() public {
vm.startPrank(Alice);
(IBridge.Message memory message, bytes memory proof) =
setUpPredefinedSuccessfulProcessMessageCall();

bool received = destChainBridge.proveMessageReceived(message, proof);

assertEq(received, true);
}

// test with a known good merkle proof / message since we cant generate
// proofs via rpc
// in foundry
Expand Down
Loading
Loading