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: implement FTI #215

Draft
wants to merge 24 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f36b538
feat: add wip, scaffold FuelMessagePortalV4 (FTI)
DefiCake Jun 7, 2024
db9d42a
feat: add Transaction event to sendTransaction in FuelMessagePortalV4
DefiCake Jun 10, 2024
c70923d
feat: update local deployments with FuelMessagePortalV4
DefiCake Jun 10, 2024
bc7fcb3
test: scaffold integration test for fti
DefiCake Jun 10, 2024
4d39381
chore: update fuel-core to 0.28 with testnet parameters
DefiCake Jun 13, 2024
41f6673
test: scaffold integration test for fti
DefiCake Jun 18, 2024
76764ad
(ci): fix pnpm audit alert and pnpm version for actions (#217)
DefiCake Jun 21, 2024
c8c9b38
chore: add changeset
DefiCake Jun 18, 2024
2c94bd7
feat: add gas price computation
DefiCake Jun 25, 2024
f0d0fc0
test: add fti min gas price test and fix flakiness
DefiCake Jun 25, 2024
6a3ef35
feat: add fee collection
DefiCake Jun 25, 2024
80139a8
test: update fti integration tests
DefiCake Jun 25, 2024
a200e69
test: complete fti unit test coverage
DefiCake Jun 25, 2024
4701e1b
test: complete fti integration tests
DefiCake Jun 25, 2024
945ac41
Merge branch 'main' into deficake/208-add-fti
DefiCake Jun 25, 2024
a6aa26d
test: pack V3 tests in a behaviour file and include it in V4 tests
DefiCake Jun 27, 2024
eb2356a
test: add outgoing messages tests to FuelMessagePortalV4
DefiCake Jun 27, 2024
afaff28
chore: remove .only from tests
DefiCake Jun 27, 2024
6691b60
feat: gas golfing and comments on sendTransaction
DefiCake Jun 27, 2024
17adc42
chore: add gas reporter
DefiCake Jun 27, 2024
0bb8d44
chore: set fuel-core to v0.31.0
DefiCake Jul 22, 2024
337153d
Merge branch 'main' into deficake/208-add-fti
DefiCake Jul 25, 2024
4d2fff0
Update packages/solidity-contracts/contracts/test/EthReceiver.sol
DefiCake Aug 1, 2024
f1c2464
chore: restore code
DefiCake Aug 1, 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
6 changes: 6 additions & 0 deletions .changeset/spicy-ghosts-call.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@fuel-bridge/solidity-contracts': minor
'@fuel-bridge/test-utils': minor
---

Add Forced Transaction Inclusion (FTI) to FuelMessagePortal
9 changes: 5 additions & 4 deletions docker/fuel-core/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# IMPORTANT! When upgrading fuel-core version,
# Make sure to check
# IMPORTANT!
# Make sure to check:
# https://github.com/FuelLabs/chain-configuration/tree/master/upgradelog/ignition
# and apply the latest state_transition_function and consensus_parameter
FROM ghcr.io/fuellabs/fuel-core:v0.28.0
# when upgrading fuel-core
FROM ghcr.io/fuellabs/fuel-core:v0.31.0

ARG FUEL_IP=0.0.0.0
ARG FUEL_PORT=4001
Expand All @@ -27,7 +28,7 @@ RUN cp -R /chain-configuration/local/* ./
# Copy the testnet consensus parameters and state transition bytecode
RUN cp /chain-configuration/upgradelog/ignition/consensus_parameters/3.json \
./latest_consensus_parameters.json
RUN cp /chain-configuration/upgradelog/ignition/state_transition_function/2.wasm \
RUN cp /chain-configuration/upgradelog/ignition/state_transition_function/5.wasm \
./state_transition_bytecode.wasm

# update local state_config with custom genesis coins config
Expand Down
225 changes: 225 additions & 0 deletions packages/integration-tests/tests/fti.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
import type { TestEnvironment } from '@fuel-bridge/test-utils';
import { setupEnvironment, waitForTransaction } from '@fuel-bridge/test-utils';
import chai from 'chai';
import { hexlify, solidityPacked } from 'ethers';
import { sha256, transactionRequestify } from 'fuels';
import type { WalletUnlocked as FuelWallet, BN } from 'fuels';

const { expect } = chai;

const MAX_GAS = 10000000n;

describe('Forced Transaction Inclusion', async function () {
// Timeout 6 minutes
const DEFAULT_TIMEOUT_MS: number = 400_000;
let BASE_ASSET_ID: string;
let CHAIN_ID: number;

let env: TestEnvironment;

// override the default test timeout of 2000ms
this.timeout(DEFAULT_TIMEOUT_MS);

before(async () => {
env = await setupEnvironment({});
BASE_ASSET_ID = env.fuel.provider.getBaseAssetId();
CHAIN_ID = env.fuel.provider.getChainId();
});

describe('Send a transaction through Ethereum', async () => {
const NUM_ETH = '0.1';
let ethSender: any;
let fuelSender: FuelWallet;
let fuelReceiver: FuelWallet;
let fuelSenderBalance: BN;
let fuelReceiverBalance: BN;

before(async () => {
ethSender;
fuelSender = env.fuel.deployer;
fuelReceiver = env.fuel.signers[1];
fuelSenderBalance = await fuelSender.getBalance(BASE_ASSET_ID);
fuelReceiverBalance = await fuelReceiver.getBalance(BASE_ASSET_ID);
});

it('allows to send transactions', async () => {
const transferRequest = await fuelSender.createTransfer(
fuelReceiver.address,
fuelSenderBalance.div(10),
BASE_ASSET_ID
);

const transactionRequest = transactionRequestify(transferRequest);
await env.fuel.provider.estimateTxDependencies(transactionRequest);

const signature = await fuelSender.signTransaction(transactionRequest);
transactionRequest.updateWitnessByOwner(fuelSender.address, signature);

const fuelSerializedTx = hexlify(transactionRequest.toTransactionBytes());

const gasPrice = await env.eth.fuelMessagePortal.MIN_GAS_PRICE();
const expectedFee = gasPrice * MAX_GAS;

const ethTx = await env.eth.fuelMessagePortal.sendTransaction(
MAX_GAS,
fuelSerializedTx,
{ value: expectedFee }
);

const { blockNumber } = await ethTx.wait();

const [event] = await env.eth.fuelMessagePortal.queryFilter(
env.eth.fuelMessagePortal.filters.Transaction,
blockNumber,
blockNumber
);

expect(event.args.canonically_serialized_tx).to.be.equal(
fuelSerializedTx
);

const payload = solidityPacked(
['uint256', 'uint64', 'bytes'],
[
event.args.nonce,
event.args.max_gas,
event.args.canonically_serialized_tx,
]
);
const relayedTxId = sha256(payload);
const fuelTxId = transactionRequest.getTransactionId(CHAIN_ID);

const { response, error } = await waitForTransaction(
fuelTxId,
env.fuel.provider,
{
relayedTxId,
}
);

if (error) {
throw new Error(error);
}

const txResult = await response.waitForResult();

expect(txResult.status).to.equal('success');
});

it('rejects transactions without signatures', async () => {
const transferRequest = await fuelSender.createTransfer(
fuelReceiver.address,
fuelSenderBalance.div(10),
BASE_ASSET_ID
);

const transactionRequest = transactionRequestify(transferRequest);
await env.fuel.provider.estimateTxDependencies(transactionRequest);

const fuelSerializedTx = hexlify(transactionRequest.toTransactionBytes());

const gasPrice = await env.eth.fuelMessagePortal.MIN_GAS_PRICE();
const expectedFee = gasPrice * MAX_GAS;

const ethTx = await env.eth.fuelMessagePortal.sendTransaction(
MAX_GAS,
fuelSerializedTx,
{ value: expectedFee }
);

const { blockNumber } = await ethTx.wait();

const [event] = await env.eth.fuelMessagePortal.queryFilter(
env.eth.fuelMessagePortal.filters.Transaction,
blockNumber,
blockNumber
);

expect(event.args.canonically_serialized_tx).to.be.equal(
fuelSerializedTx
);

const payload = solidityPacked(
['uint256', 'uint64', 'bytes'],
[
event.args.nonce,
event.args.max_gas,
event.args.canonically_serialized_tx,
]
);
const relayedTxId = sha256(payload);
const fuelTxId = transactionRequest.getTransactionId(CHAIN_ID);

const { response, error } = await waitForTransaction(
fuelTxId,
env.fuel.provider,
{
relayedTxId,
}
);

expect(response).to.be.null;
expect(error).to.contain('InvalidSignature');
});

it('rejects transactions without enough gas', async () => {
const transferRequest = await fuelSender.createTransfer(
fuelReceiver.address,
fuelSenderBalance.div(10),
BASE_ASSET_ID
);

const transactionRequest = transactionRequestify(transferRequest);
await env.fuel.provider.estimateTxDependencies(transactionRequest);

const signature = await fuelSender.signTransaction(transactionRequest);
transactionRequest.updateWitnessByOwner(fuelSender.address, signature);

const fuelSerializedTx = hexlify(transactionRequest.toTransactionBytes());

const gasPrice = await env.eth.fuelMessagePortal.MIN_GAS_PRICE();
const minGasPerTx = await env.eth.fuelMessagePortal.MIN_GAS_PER_TX();
const expectedFee = gasPrice * minGasPerTx;

const ethTx = await env.eth.fuelMessagePortal.sendTransaction(
minGasPerTx,
fuelSerializedTx,
{ value: expectedFee }
);

const { blockNumber } = await ethTx.wait();

const [event] = await env.eth.fuelMessagePortal.queryFilter(
env.eth.fuelMessagePortal.filters.Transaction,
blockNumber,
blockNumber
);

expect(event.args.canonically_serialized_tx).to.be.equal(
fuelSerializedTx
);

const payload = solidityPacked(
['uint256', 'uint64', 'bytes'],
[
event.args.nonce,
event.args.max_gas,
event.args.canonically_serialized_tx,
]
);
const relayedTxId = sha256(payload);
const fuelTxId = transactionRequest.getTransactionId(CHAIN_ID);

const { response, error } = await waitForTransaction(
fuelTxId,
env.fuel.provider,
{
relayedTxId,
}
);

expect(response).to.be.null;
expect(error).to.contain('Insufficient');
});
});
});
Loading
Loading