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

Release v0.21.0 for beta 5 #1513

Merged
merged 5 commits into from
Nov 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
76 changes: 51 additions & 25 deletions CHANGELOG.md

Large diffs are not rendered by default.

247 changes: 107 additions & 140 deletions Cargo.lock

Large diffs are not rendered by default.

48 changes: 24 additions & 24 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,33 +46,33 @@ homepage = "https://fuel.network/"
keywords = ["blockchain", "cryptocurrencies", "fuel-vm", "vm"]
license = "BUSL-1.1"
repository = "https://github.com/FuelLabs/fuel-core"
version = "0.21.0-rc.1"
version = "0.21.0"

[workspace.dependencies]
# Workspace members
fuel-core = { version = "0.21.0-rc.1", path = "./crates/fuel-core", default-features = false }
fuel-core-client-bin = { version = "0.21.0-rc.1", path = "./bin/client" }
fuel-core-bin = { version = "0.21.0-rc.1", path = "./bin/fuel-core" }
fuel-core-keygen = { version = "0.21.0-rc.1", path = "./crates/keygen" }
fuel-core-keygen-bin = { version = "0.21.0-rc.1", path = "./bin/keygen" }
fuel-core-chain-config = { version = "0.21.0-rc.1", path = "./crates/chain-config" }
fuel-core-client = { version = "0.21.0-rc.1", path = "./crates/client" }
fuel-core-database = { version = "0.21.0-rc.1", path = "./crates/database" }
fuel-core-metrics = { version = "0.21.0-rc.1", path = "./crates/metrics" }
fuel-core-services = { version = "0.21.0-rc.1", path = "./crates/services" }
fuel-core-consensus-module = { version = "0.21.0-rc.1", path = "./crates/services/consensus_module" }
fuel-core-bft = { version = "0.21.0-rc.1", path = "./crates/services/consensus_module/bft" }
fuel-core-poa = { version = "0.21.0-rc.1", path = "./crates/services/consensus_module/poa" }
fuel-core-executor = { version = "0.21.0-rc.1", path = "./crates/services/executor" }
fuel-core-importer = { version = "0.21.0-rc.1", path = "./crates/services/importer" }
fuel-core-p2p = { version = "0.21.0-rc.1", path = "./crates/services/p2p" }
fuel-core-producer = { version = "0.21.0-rc.1", path = "./crates/services/producer" }
fuel-core-relayer = { version = "0.21.0-rc.1", path = "./crates/services/relayer" }
fuel-core-sync = { version = "0.21.0-rc.1", path = "./crates/services/sync" }
fuel-core-txpool = { version = "0.21.0-rc.1", path = "./crates/services/txpool" }
fuel-core-storage = { version = "0.21.0-rc.1", path = "./crates/storage" }
fuel-core-trace = { version = "0.21.0-rc.1", path = "./crates/trace" }
fuel-core-types = { version = "0.21.0-rc.1", path = "./crates/types", default-features = false }
fuel-core = { version = "0.21.0", path = "./crates/fuel-core", default-features = false }
fuel-core-client-bin = { version = "0.21.0", path = "./bin/client" }
fuel-core-bin = { version = "0.21.0", path = "./bin/fuel-core" }
fuel-core-keygen = { version = "0.21.0", path = "./crates/keygen" }
fuel-core-keygen-bin = { version = "0.21.0", path = "./bin/keygen" }
fuel-core-chain-config = { version = "0.21.0", path = "./crates/chain-config" }
fuel-core-client = { version = "0.21.0", path = "./crates/client" }
fuel-core-database = { version = "0.21.0", path = "./crates/database" }
fuel-core-metrics = { version = "0.21.0", path = "./crates/metrics" }
fuel-core-services = { version = "0.21.0", path = "./crates/services" }
fuel-core-consensus-module = { version = "0.21.0", path = "./crates/services/consensus_module" }
fuel-core-bft = { version = "0.21.0", path = "./crates/services/consensus_module/bft" }
fuel-core-poa = { version = "0.21.0", path = "./crates/services/consensus_module/poa" }
fuel-core-executor = { version = "0.21.0", path = "./crates/services/executor" }
fuel-core-importer = { version = "0.21.0", path = "./crates/services/importer" }
fuel-core-p2p = { version = "0.21.0", path = "./crates/services/p2p" }
fuel-core-producer = { version = "0.21.0", path = "./crates/services/producer" }
fuel-core-relayer = { version = "0.21.0", path = "./crates/services/relayer" }
fuel-core-sync = { version = "0.21.0", path = "./crates/services/sync" }
fuel-core-txpool = { version = "0.21.0", path = "./crates/services/txpool" }
fuel-core-storage = { version = "0.21.0", path = "./crates/storage" }
fuel-core-trace = { version = "0.21.0", path = "./crates/trace" }
fuel-core-types = { version = "0.21.0", path = "./crates/types", default-features = false }
fuel-core-tests = { version = "0.0.0", path = "./tests" }
fuel-core-xtask = { version = "0.0.0", path = "./xtask" }

Expand Down
3 changes: 0 additions & 3 deletions crates/chain-config/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,10 @@ serde_with = "1.11"
tracing = "0.1"

[dev-dependencies]
fuel-core = { workspace = true }
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It causes cycle dependencies during publishing rust-lang/cargo#4242.

fuel-core-client = { workspace = true }
fuel-core-types = { workspace = true, default-features = false, features = ["random", "serde"] }
insta = { workspace = true }
rand = { workspace = true }
serde_json = { version = "1.0", features = ["raw_value"] }
tokio = { workspace = true, features = ["full"] }

[features]
default = ["std"]
Expand Down
324 changes: 0 additions & 324 deletions crates/chain-config/src/fee_collection_contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,327 +65,3 @@ pub fn generate(address: Address) -> Vec<u8> {

asm_bytes
}

#[cfg(test)]
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::arithmetic_side_effects)]
mod tests {
use super::*;
use crate::SecretKey;

use rand::{
rngs::StdRng,
Rng,
SeedableRng,
};

use fuel_core::service::{
Config,
FuelService,
};
use fuel_core_client::client::{
types::TransactionStatus,
FuelClient,
};
use fuel_core_types::{
fuel_asm::GTFArgs,
fuel_tx::{
Cacheable,
Finalizable,
Input,
Output,
TransactionBuilder,
Witness,
},
fuel_types::{
canonical::Serialize,
AssetId,
BlockHeight,
ChainId,
ContractId,
Salt,
},
};

struct TestContext {
address: Address,
contract_id: ContractId,
_node: FuelService,
client: FuelClient,
}

async fn setup(rng: &mut StdRng) -> TestContext {
// Make contract that coinbase fees are collected into
let address: Address = rng.gen();
let salt: Salt = rng.gen();
let contract = generate(address);
let witness: Witness = contract.into();
let mut create_tx = TransactionBuilder::create(witness.clone(), salt, vec![])
.add_random_fee_input()
.finalize();
create_tx
.precompute(&ChainId::default())
.expect("tx should be valid");
let contract_id = create_tx.metadata().as_ref().unwrap().contract_id;

// Start up a node
let mut config = Config::local_node();
config.debug = true;
config.block_producer.coinbase_recipient = Some(contract_id);
let node = FuelService::new_node(config).await.unwrap();
let client = FuelClient::from(node.bound_address);

// Submit contract creation tx
let tx_status = client
.submit_and_await_commit(&create_tx.into())
.await
.unwrap();
assert!(matches!(tx_status, TransactionStatus::Success { .. }));
let bh = client.produce_blocks(1, None).await.unwrap();
assert_eq!(bh, BlockHeight::new(2));

// No fees should have been collected yet
let contract_balance =
client.contract_balance(&(contract_id), None).await.unwrap();
assert_eq!(contract_balance, 0);

TestContext {
address,
contract_id,
_node: node,
client,
}
}

/// This makes a block with a single transaction that has a fee,
/// so that the coinbase fee is collected into the contract
async fn make_block_with_fee(rng: &mut StdRng, ctx: &TestContext) {
let old_balance = ctx
.client
.contract_balance(&ctx.contract_id, None)
.await
.unwrap();

// Run a script that does nothing, but will cause fee collection
let tx = TransactionBuilder::script(
[op::ret(RegId::ONE)].into_iter().collect(),
vec![],
)
.add_unsigned_coin_input(
SecretKey::random(rng),
rng.gen(),
1000,
Default::default(),
Default::default(),
Default::default(),
)
.gas_price(1)
.script_gas_limit(1_000_000)
.finalize_as_transaction();
let tx_status = ctx.client.submit_and_await_commit(&tx).await.unwrap();
assert!(matches!(tx_status, TransactionStatus::Success { .. }));

// Now the coinbase fee should be reflected in the contract balance
let new_balance = ctx
.client
.contract_balance(&ctx.contract_id, None)
.await
.unwrap();
assert!(new_balance > old_balance);
}

async fn collect_fees(ctx: &TestContext) {
let TestContext {
client,
contract_id,
..
} = ctx;

let asset_id = AssetId::BASE;
let output_index = 1u64;
let call_struct_register = 0x10;
// Now call the fee collection contract to withdraw the fees
let script = vec![
// Point to the call structure
op::gtf_args(call_struct_register, 0x00, GTFArgs::ScriptData),
op::addi(
call_struct_register,
call_struct_register,
(asset_id.size() + output_index.size()) as u16,
),
op::call(call_struct_register, RegId::ZERO, RegId::ZERO, RegId::CGAS),
op::ret(RegId::ONE),
];

let tx = TransactionBuilder::script(
script.into_iter().collect(),asset_id.to_bytes().into_iter()
.chain(output_index.to_bytes().into_iter())
.chain(contract_id
.to_bytes().into_iter())
.chain(0u64.to_bytes().into_iter())
.chain(0u64.to_bytes().into_iter())
.collect(),
)
.add_random_fee_input() // No coinbase fee for this block
.gas_price(0)
.script_gas_limit(1_000_000)
.add_input(Input::contract(
Default::default(),
Default::default(),
Default::default(),
Default::default(),
*contract_id,
))
.add_output(Output::contract(1, Default::default(), Default::default()))
.add_output(Output::variable(
Default::default(),
Default::default(),
Default::default(),
))
.finalize_as_transaction();

let tx_status = client.submit_and_await_commit(&tx).await.unwrap();
assert!(
matches!(tx_status, TransactionStatus::Success { .. }),
"{tx_status:?}"
);
}

#[tokio::test]
async fn happy_path() {
let rng = &mut StdRng::seed_from_u64(0);

let ctx = setup(rng).await;

for _ in 0..10 {
make_block_with_fee(rng, &ctx).await;
}

// When
// Before withdrawal, the recipient's balance should be zero,
// and the contract balance should be non-zero.
let contract_balance_before_collect = ctx
.client
.contract_balance(&ctx.contract_id, None)
.await
.unwrap();
assert_ne!(contract_balance_before_collect, 0);
assert_eq!(ctx.client.balance(&ctx.address, None).await.unwrap(), 0);

// When
collect_fees(&ctx).await;

// Then

// Make sure that the full balance was been withdrawn
let contract_balance_after_collect = ctx
.client
.contract_balance(&ctx.contract_id, None)
.await
.unwrap();
assert_eq!(contract_balance_after_collect, 0);

// Make sure that the full balance was been withdrawn
assert_eq!(
ctx.client.balance(&ctx.address, None).await.unwrap(),
contract_balance_before_collect
);
}

/// Attempts fee collection when no balance has accumulated yet
#[tokio::test]
async fn no_fees_collected_yet() {
let rng = &mut StdRng::seed_from_u64(0);

let ctx = setup(rng).await;

// Given
let contract_balance_before_collect = ctx
.client
.contract_balance(&ctx.contract_id, None)
.await
.unwrap();
assert_eq!(contract_balance_before_collect, 0);
assert_eq!(ctx.client.balance(&ctx.address, None).await.unwrap(), 0);

// When
collect_fees(&ctx).await;

// Then

// Make sure that the balance is still zero
let contract_balance = ctx
.client
.contract_balance(&ctx.contract_id, None)
.await
.unwrap();
assert_eq!(contract_balance, 0);

// There were no coins to withdraw
assert_eq!(ctx.client.balance(&ctx.address, None).await.unwrap(), 0);
}

#[tokio::test]
async fn missing_variable_output() {
let rng = &mut StdRng::seed_from_u64(0);

let ctx = setup(rng).await;
make_block_with_fee(rng, &ctx).await;

let asset_id = AssetId::BASE;
let output_index = 1u64;
let call_struct_register = 0x10;

// Now call the fee collection contract to withdraw the fees,
// but unlike in the happy path, we don't add the variable output to the transaction.
let script = vec![
// Point to the call structure
op::gtf_args(call_struct_register, 0x00, GTFArgs::ScriptData),
op::addi(
call_struct_register,
call_struct_register,
(asset_id.size() + output_index.size()) as u16,
),
op::call(call_struct_register, RegId::ZERO, RegId::ZERO, RegId::CGAS),
op::ret(RegId::ONE),
];
let tx = TransactionBuilder::script(
script.into_iter().collect(),
asset_id.to_bytes().into_iter()
.chain(output_index.to_bytes().into_iter())
.chain(ctx.contract_id
.to_bytes().into_iter())
.chain(0u64.to_bytes().into_iter())
.chain(0u64.to_bytes().into_iter())
.collect(),
)
.add_random_fee_input() // No coinbase fee for this block
.gas_price(0)
.script_gas_limit(1_000_000)
.add_input(Input::contract(
Default::default(),
Default::default(),
Default::default(),
Default::default(),
ctx.contract_id,
))
.add_output(Output::contract(1, Default::default(), Default::default()))
.finalize_as_transaction();

let tx_status = ctx.client.submit_and_await_commit(&tx).await.unwrap();
let TransactionStatus::Failure { reason, .. } = tx_status else {
panic!("Expected failure");
};
assert_eq!(reason, "OutputNotFound");

// Make sure that nothing was withdrawn
let contract_balance = ctx
.client
.contract_balance(&ctx.contract_id, None)
.await
.unwrap();
assert_eq!(contract_balance, 1);
let asset_balance = ctx.client.balance(&ctx.address, None).await.unwrap();
assert_eq!(asset_balance, 0);
}
}
Loading
Loading