mirror of
https://github.com/FuelLabs/sway.git
synced 2025-08-10 05:39:05 +00:00

Some checks failed
Codspeed Benchmarks / benchmarks (push) Has been cancelled
CI / check-dependency-version-formats (push) Has been cancelled
CI / check-forc-manifest-version (push) Has been cancelled
CI / get-fuel-core-version (push) Has been cancelled
CI / build-sway-lib-std (push) Has been cancelled
CI / build-sway-examples (push) Has been cancelled
CI / build-reference-examples (push) Has been cancelled
CI / forc-fmt-check-sway-lib-std (push) Has been cancelled
CI / forc-fmt-check-sway-examples (push) Has been cancelled
CI / forc-fmt-check-panic (push) Has been cancelled
CI / check-sdk-harness-test-suite-compatibility (push) Has been cancelled
CI / build-mdbook (push) Has been cancelled
CI / build-forc-doc-sway-lib-std (push) Has been cancelled
CI / cargo-clippy (push) Has been cancelled
CI / build-forc-test-project (push) Has been cancelled
CI / cargo-build-workspace (push) Has been cancelled
CI / cargo-toml-fmt-check (push) Has been cancelled
CI / cargo-fmt-check (push) Has been cancelled
CI / forc-run-benchmarks (push) Has been cancelled
CI / forc-unit-tests (push) Has been cancelled
CI / forc-pkg-fuels-deps-check (push) Has been cancelled
CI / cargo-test-sway-lsp (push) Has been cancelled
CI / cargo-run-e2e-test-evm (push) Has been cancelled
CI / cargo-test-lib-std (push) Has been cancelled
CI / cargo-test-forc (push) Has been cancelled
CI / cargo-test-workspace (push) Has been cancelled
CI / cargo-unused-deps-check (push) Has been cancelled
CI / pre-publish-check (push) Has been cancelled
github pages / deploy (push) Has been cancelled
CI / verifications-complete (push) Has been cancelled
CI / cargo-run-e2e-test (push) Has been cancelled
CI / cargo-run-e2e-test-release (push) Has been cancelled
CI / cargo-test-forc-debug (push) Has been cancelled
CI / cargo-test-forc-client (push) Has been cancelled
CI / cargo-test-forc-mcp (push) Has been cancelled
CI / cargo-test-forc-node (push) Has been cancelled
CI / notify-slack-on-failure (push) Has been cancelled
CI / publish (push) Has been cancelled
CI / publish-sway-lib-std (push) Has been cancelled
CI / Build and upload forc binaries to release (push) Has been cancelled
This is in preparation for the argument mutability analysis. This PR also has some skeleton code for this analysis, but the actual analysis is still a `todo!()`. The analysis will also have better precision if we use fewer asm blocks, but I'll get to that after working on the analysis itself.
1340 lines
52 KiB
Rust
1340 lines
52 KiB
Rust
use forc::cli::shared::Pkg;
|
|
use forc_client::{
|
|
cmd,
|
|
op::{deploy, DeployedContract, DeployedExecutable, DeployedPackage},
|
|
util::{account::ForcClientAccount, tx::update_proxy_contract_target},
|
|
NodeTarget,
|
|
};
|
|
use forc_pkg::manifest::Proxy;
|
|
use fuel_crypto::SecretKey;
|
|
use fuel_tx::{ContractId, Salt};
|
|
use fuels::{
|
|
macros::abigen,
|
|
types::{transaction::TxPolicies, AsciiString, Bits256, SizedAsciiString},
|
|
};
|
|
use fuels_accounts::{
|
|
provider::Provider, signers::private_key::PrivateKeySigner, wallet::Wallet, Account,
|
|
ViewOnlyAccount,
|
|
};
|
|
use portpicker::Port;
|
|
use rand::thread_rng;
|
|
use rexpect::spawn;
|
|
use std::{
|
|
fs,
|
|
path::{Path, PathBuf},
|
|
process::{Child, Command},
|
|
str::FromStr,
|
|
};
|
|
use tempfile::tempdir;
|
|
use toml_edit::{value, DocumentMut, InlineTable, Item, Table, Value};
|
|
|
|
fn get_workspace_root() -> PathBuf {
|
|
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
|
.join("../")
|
|
.join("../")
|
|
.canonicalize()
|
|
.unwrap()
|
|
}
|
|
|
|
fn test_data_path() -> PathBuf {
|
|
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
|
.join("test")
|
|
.join("data")
|
|
.canonicalize()
|
|
.unwrap()
|
|
}
|
|
|
|
fn run_node() -> (Child, Port) {
|
|
let port = portpicker::pick_unused_port().expect("No ports free");
|
|
|
|
let child = Command::new("fuel-core")
|
|
.arg("run")
|
|
.arg("--debug")
|
|
.arg("--db-type")
|
|
.arg("in-memory")
|
|
.arg("--port")
|
|
.arg(port.to_string())
|
|
.spawn()
|
|
.expect("Failed to start fuel-core");
|
|
(child, port)
|
|
}
|
|
|
|
/// Copy a directory recursively from `source` to `dest`.
|
|
fn copy_dir(source: &Path, dest: &Path) -> anyhow::Result<()> {
|
|
fs::create_dir_all(dest)?;
|
|
for e in fs::read_dir(source)? {
|
|
let entry = e?;
|
|
let file_type = entry.file_type()?;
|
|
if file_type.is_dir() {
|
|
copy_dir(&entry.path(), &dest.join(entry.file_name()))?;
|
|
} else {
|
|
fs::copy(entry.path(), dest.join(entry.file_name()))?;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Tries to get an `DeployedContract` out of the given `DeployedPackage`.
|
|
/// Panics otherwise.
|
|
fn expect_deployed_contract(deployed_package: DeployedPackage) -> DeployedContract {
|
|
if let DeployedPackage::Contract(contract) = deployed_package {
|
|
contract
|
|
} else {
|
|
println!("{deployed_package:?}");
|
|
panic!("expected deployed package to be a contract")
|
|
}
|
|
}
|
|
|
|
/// Tries to get a script (`DeployedExecutable`) out of given deployed package.
|
|
/// Panics otherwise.
|
|
fn expect_deployed_script(deployed_package: DeployedPackage) -> DeployedExecutable {
|
|
if let DeployedPackage::Script(script) = deployed_package {
|
|
script
|
|
} else {
|
|
panic!("expected deployed package to be a script")
|
|
}
|
|
}
|
|
|
|
/// Tries to get a predicate (`DeployedExecutable`) out of given deployed package.
|
|
/// Panics otherwise.
|
|
fn expect_deployed_predicate(deployed_package: DeployedPackage) -> DeployedExecutable {
|
|
if let DeployedPackage::Predicate(predicate) = deployed_package {
|
|
predicate
|
|
} else {
|
|
panic!("expected deployed package to be a predicate")
|
|
}
|
|
}
|
|
|
|
fn patch_manifest_file_with_path_std(manifest_dir: &Path) -> anyhow::Result<()> {
|
|
let toml_path = manifest_dir.join(sway_utils::constants::MANIFEST_FILE_NAME);
|
|
let toml_content = fs::read_to_string(&toml_path).unwrap();
|
|
|
|
let mut doc = toml_content.parse::<DocumentMut>().unwrap();
|
|
let new_std_path = get_workspace_root().join("sway-lib-std");
|
|
|
|
let mut std_dependency = InlineTable::new();
|
|
std_dependency.insert("path", Value::from(new_std_path.display().to_string()));
|
|
doc["dependencies"]["std"] = Item::Value(Value::InlineTable(std_dependency));
|
|
|
|
fs::write(&toml_path, doc.to_string()).unwrap();
|
|
Ok(())
|
|
}
|
|
|
|
fn patch_manifest_file_with_proxy_table(manifest_dir: &Path, proxy: Proxy) -> anyhow::Result<()> {
|
|
let toml_path = manifest_dir.join(sway_utils::constants::MANIFEST_FILE_NAME);
|
|
let toml_content = fs::read_to_string(&toml_path)?;
|
|
let mut doc = toml_content.parse::<DocumentMut>()?;
|
|
|
|
let proxy_table = doc.entry("proxy").or_insert(Item::Table(Table::new()));
|
|
let proxy_table = proxy_table.as_table_mut().unwrap();
|
|
|
|
proxy_table.insert("enabled", value(proxy.enabled));
|
|
|
|
if let Some(address) = proxy.address {
|
|
proxy_table.insert("address", value(address));
|
|
} else {
|
|
proxy_table.remove("address");
|
|
}
|
|
|
|
fs::write(&toml_path, doc.to_string())?;
|
|
Ok(())
|
|
}
|
|
|
|
fn update_main_sw(tmp_dir: &Path) -> anyhow::Result<()> {
|
|
let main_sw_path = tmp_dir.join("src").join("main.sw");
|
|
let content = fs::read_to_string(&main_sw_path)?;
|
|
let updated_content = content.replace("true", "false");
|
|
fs::write(main_sw_path, updated_content)?;
|
|
Ok(())
|
|
}
|
|
|
|
async fn assert_big_contract_calls(wallet: Wallet, contract_id: ContractId) {
|
|
abigen!(Contract(
|
|
name = "BigContract",
|
|
abi = "forc-plugins/forc-client/test/data/big_contract/big_contract-abi.json"
|
|
));
|
|
|
|
let instance = BigContract::new(contract_id, wallet);
|
|
|
|
let result = instance.methods().large_blob().call().await.unwrap().value;
|
|
assert!(result);
|
|
|
|
let result = instance
|
|
.methods()
|
|
.enum_input_output(Location::Mars)
|
|
.call()
|
|
.await
|
|
.unwrap()
|
|
.value;
|
|
assert_eq!(result, Location::Mars);
|
|
|
|
// Test enum with "tuple like struct" with simple value.
|
|
let result = instance
|
|
.methods()
|
|
.enum_input_output(Location::Earth(u64::MAX))
|
|
.call()
|
|
.await
|
|
.unwrap()
|
|
.value;
|
|
assert_eq!(result, Location::Earth(u64::MAX));
|
|
|
|
// Test enum with "tuple like struct" with enum value.
|
|
let result = instance
|
|
.methods()
|
|
.enum_input_output(Location::SimpleJupiter(Color::Red))
|
|
.call()
|
|
.await
|
|
.unwrap()
|
|
.value;
|
|
assert_eq!(result, Location::SimpleJupiter(Color::Red));
|
|
|
|
// Test enum with "tuple like struct" with enum value.
|
|
let result = instance
|
|
.methods()
|
|
.enum_input_output(Location::SimpleJupiter(Color::Blue(u64::MAX)))
|
|
.call()
|
|
.await
|
|
.unwrap()
|
|
.value;
|
|
assert_eq!(result, Location::SimpleJupiter(Color::Blue(u64::MAX)));
|
|
|
|
// Test enum with "tuple like struct" with enum array value.
|
|
let result = instance
|
|
.methods()
|
|
.enum_input_output(Location::Jupiter([Color::Red, Color::Blue(u64::MAX)]))
|
|
.call()
|
|
.await
|
|
.unwrap()
|
|
.value;
|
|
assert_eq!(
|
|
result,
|
|
Location::Jupiter([Color::Red, Color::Blue(u64::MAX)])
|
|
);
|
|
|
|
// Test enum with "tuple like struct" with struct array value.
|
|
let result = instance
|
|
.methods()
|
|
.enum_input_output(Location::SimplePluto(SimpleStruct {
|
|
a: true,
|
|
b: u64::MAX,
|
|
}))
|
|
.call()
|
|
.await
|
|
.unwrap()
|
|
.value;
|
|
assert_eq!(
|
|
result,
|
|
Location::SimplePluto(SimpleStruct {
|
|
a: true,
|
|
b: u64::MAX,
|
|
})
|
|
);
|
|
|
|
let input = Person {
|
|
name: AsciiString::new("Alice".into()).unwrap(),
|
|
age: 42,
|
|
alive: true,
|
|
location: Location::Earth(1),
|
|
some_tuple: (false, 42),
|
|
some_array: [4, 2],
|
|
some_b_256: Bits256::zeroed(),
|
|
};
|
|
let result = instance
|
|
.methods()
|
|
.struct_input_output(input.clone())
|
|
.call()
|
|
.await
|
|
.unwrap()
|
|
.value;
|
|
assert_eq!(result, input);
|
|
|
|
let _ = instance
|
|
.methods()
|
|
.push_storage_u16(42)
|
|
.call()
|
|
.await
|
|
.unwrap();
|
|
let result = instance
|
|
.methods()
|
|
.get_storage_u16(0)
|
|
.call()
|
|
.await
|
|
.unwrap()
|
|
.value;
|
|
assert_eq!(result, 42);
|
|
|
|
let _ = instance
|
|
.methods()
|
|
.push_storage_simple(SimpleStruct {
|
|
a: true,
|
|
b: u64::MAX,
|
|
})
|
|
.call()
|
|
.await
|
|
.unwrap();
|
|
let result = instance
|
|
.methods()
|
|
.get_storage_simple(0)
|
|
.call()
|
|
.await
|
|
.unwrap()
|
|
.value;
|
|
assert_eq!(
|
|
result,
|
|
SimpleStruct {
|
|
a: true,
|
|
b: u64::MAX,
|
|
}
|
|
);
|
|
|
|
let _ = instance
|
|
.methods()
|
|
.push_storage_location(Location::Mars)
|
|
.call()
|
|
.await
|
|
.unwrap();
|
|
let result = instance
|
|
.methods()
|
|
.get_storage_location(0)
|
|
.call()
|
|
.await
|
|
.unwrap()
|
|
.value;
|
|
assert_eq!(result, Location::Mars);
|
|
|
|
let _ = instance
|
|
.methods()
|
|
.push_storage_location(Location::Earth(u64::MAX))
|
|
.call()
|
|
.await
|
|
.unwrap();
|
|
let result = instance
|
|
.methods()
|
|
.get_storage_location(1)
|
|
.call()
|
|
.await
|
|
.unwrap()
|
|
.value;
|
|
assert_eq!(result, Location::Earth(u64::MAX));
|
|
|
|
let _ = instance
|
|
.methods()
|
|
.push_storage_location(Location::Jupiter([Color::Red, Color::Blue(u64::MAX)]))
|
|
.call()
|
|
.await
|
|
.unwrap();
|
|
let result = instance
|
|
.methods()
|
|
.get_storage_location(2)
|
|
.call()
|
|
.await
|
|
.unwrap()
|
|
.value;
|
|
assert_eq!(
|
|
result,
|
|
Location::Jupiter([Color::Red, Color::Blue(u64::MAX)])
|
|
);
|
|
|
|
let result = instance
|
|
.methods()
|
|
.assert_configurables()
|
|
.call()
|
|
.await
|
|
.unwrap()
|
|
.value;
|
|
assert!(result);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_simple_deploy() {
|
|
let (mut node, port) = run_node();
|
|
let tmp_dir = tempdir().unwrap();
|
|
let project_dir = test_data_path().join("standalone_contract");
|
|
copy_dir(&project_dir, tmp_dir.path()).unwrap();
|
|
patch_manifest_file_with_path_std(tmp_dir.path()).unwrap();
|
|
|
|
let pkg = Pkg {
|
|
path: Some(tmp_dir.path().display().to_string()),
|
|
..Default::default()
|
|
};
|
|
|
|
let node_url = format!("http://127.0.0.1:{}/v1/graphql", port);
|
|
let target = NodeTarget {
|
|
node_url: Some(node_url),
|
|
target: None,
|
|
testnet: false,
|
|
mainnet: false,
|
|
devnet: false,
|
|
};
|
|
let cmd = cmd::Deploy {
|
|
pkg,
|
|
salt: Some(vec![format!("{}", Salt::default())]),
|
|
node: target,
|
|
default_signer: true,
|
|
..Default::default()
|
|
};
|
|
let contract_ids = deploy(cmd).await.unwrap();
|
|
node.kill().unwrap();
|
|
let expected = vec![DeployedPackage::Contract(DeployedContract {
|
|
id: ContractId::from_str(
|
|
"440b559604961bdbeaa31421823f34e45d246b5d139aa4886e5e1cf2901fc925",
|
|
)
|
|
.unwrap(),
|
|
proxy: None,
|
|
chunked: false,
|
|
})];
|
|
|
|
assert_eq!(contract_ids, expected)
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_deploy_submit_only() {
|
|
let (mut node, port) = run_node();
|
|
let tmp_dir = tempdir().unwrap();
|
|
let project_dir = test_data_path().join("standalone_contract");
|
|
copy_dir(&project_dir, tmp_dir.path()).unwrap();
|
|
patch_manifest_file_with_path_std(tmp_dir.path()).unwrap();
|
|
|
|
let pkg = Pkg {
|
|
path: Some(tmp_dir.path().display().to_string()),
|
|
..Default::default()
|
|
};
|
|
|
|
let node_url = format!("http://127.0.0.1:{}/v1/graphql", port);
|
|
|
|
let target = NodeTarget {
|
|
node_url: Some(node_url),
|
|
target: None,
|
|
testnet: false,
|
|
mainnet: false,
|
|
devnet: false,
|
|
};
|
|
let cmd = cmd::Deploy {
|
|
pkg,
|
|
salt: Some(vec![format!("{}", Salt::default())]),
|
|
node: target,
|
|
default_signer: true,
|
|
submit_only: true,
|
|
..Default::default()
|
|
};
|
|
let contract_ids = deploy(cmd).await.unwrap();
|
|
node.kill().unwrap();
|
|
let expected = vec![DeployedPackage::Contract(DeployedContract {
|
|
id: ContractId::from_str(
|
|
"440b559604961bdbeaa31421823f34e45d246b5d139aa4886e5e1cf2901fc925",
|
|
)
|
|
.unwrap(),
|
|
proxy: None,
|
|
chunked: false,
|
|
})];
|
|
|
|
assert_eq!(contract_ids, expected)
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_deploy_fresh_proxy() {
|
|
let (mut node, port) = run_node();
|
|
let tmp_dir = tempdir().unwrap();
|
|
let project_dir = test_data_path().join("standalone_contract");
|
|
copy_dir(&project_dir, tmp_dir.path()).unwrap();
|
|
patch_manifest_file_with_path_std(tmp_dir.path()).unwrap();
|
|
let proxy = Proxy {
|
|
enabled: true,
|
|
address: None,
|
|
};
|
|
patch_manifest_file_with_proxy_table(tmp_dir.path(), proxy).unwrap();
|
|
|
|
let pkg = Pkg {
|
|
path: Some(tmp_dir.path().display().to_string()),
|
|
..Default::default()
|
|
};
|
|
|
|
let node_url = format!("http://127.0.0.1:{}/v1/graphql", port);
|
|
let target = NodeTarget {
|
|
node_url: Some(node_url),
|
|
target: None,
|
|
testnet: false,
|
|
mainnet: false,
|
|
devnet: false,
|
|
};
|
|
let cmd = cmd::Deploy {
|
|
pkg,
|
|
salt: Some(vec![format!("{}", Salt::default())]),
|
|
node: target,
|
|
default_signer: true,
|
|
..Default::default()
|
|
};
|
|
let contract_ids = deploy(cmd).await.unwrap();
|
|
node.kill().unwrap();
|
|
let impl_contract = DeployedPackage::Contract(DeployedContract {
|
|
id: ContractId::from_str(
|
|
"440b559604961bdbeaa31421823f34e45d246b5d139aa4886e5e1cf2901fc925",
|
|
)
|
|
.unwrap(),
|
|
proxy: Some(
|
|
ContractId::from_str(
|
|
"19d465200575ebd085300242002efcda38db99e22449a5c1346588efe9ced7f7",
|
|
)
|
|
.unwrap(),
|
|
),
|
|
chunked: false,
|
|
});
|
|
let expected = vec![impl_contract];
|
|
|
|
assert_eq!(contract_ids, expected)
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_proxy_contract_re_routes_call() {
|
|
let (mut node, port) = run_node();
|
|
let tmp_dir = tempdir().unwrap();
|
|
let project_dir = test_data_path().join("standalone_contract");
|
|
copy_dir(&project_dir, tmp_dir.path()).unwrap();
|
|
patch_manifest_file_with_path_std(tmp_dir.path()).unwrap();
|
|
let proxy = Proxy {
|
|
enabled: true,
|
|
address: None,
|
|
};
|
|
patch_manifest_file_with_proxy_table(tmp_dir.path(), proxy).unwrap();
|
|
|
|
let pkg = Pkg {
|
|
path: Some(tmp_dir.path().display().to_string()),
|
|
..Default::default()
|
|
};
|
|
|
|
let node_url = format!("http://127.0.0.1:{}/v1/graphql", port);
|
|
let target = NodeTarget {
|
|
node_url: Some(node_url.clone()),
|
|
target: None,
|
|
testnet: false,
|
|
mainnet: false,
|
|
devnet: false,
|
|
};
|
|
let cmd = cmd::Deploy {
|
|
pkg,
|
|
salt: Some(vec![format!("{}", Salt::default())]),
|
|
node: target,
|
|
default_signer: true,
|
|
..Default::default()
|
|
};
|
|
let deployed_contract = expect_deployed_contract(deploy(cmd).await.unwrap().remove(0));
|
|
// At this point we deployed a contract with proxy.
|
|
let proxy_contract_id = deployed_contract.proxy.unwrap();
|
|
let impl_contract_id = deployed_contract.id;
|
|
// Make a contract call into proxy contract, and check if the initial
|
|
// contract returns a true.
|
|
let provider = Provider::connect(&node_url).await.unwrap();
|
|
let secret_key = SecretKey::from_str(forc_client::constants::DEFAULT_PRIVATE_KEY).unwrap();
|
|
let signer = PrivateKeySigner::new(secret_key);
|
|
let wallet_unlocked = Wallet::new(signer, provider);
|
|
|
|
abigen!(Contract(
|
|
name = "ImplementationContract",
|
|
abi = "forc-plugins/forc-client/test/data/standalone_contract/standalone_contract-abi.json"
|
|
));
|
|
|
|
let impl_contract_a = ImplementationContract::new(proxy_contract_id, wallet_unlocked.clone());
|
|
|
|
// Test storage functions
|
|
let res = impl_contract_a
|
|
.methods()
|
|
.test_function_read()
|
|
.with_contract_ids(&[impl_contract_id])
|
|
.call()
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(res.value, 5);
|
|
let res = impl_contract_a
|
|
.methods()
|
|
.test_function_write(8)
|
|
.with_contract_ids(&[impl_contract_id])
|
|
.call()
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(res.value, 8);
|
|
|
|
let res = impl_contract_a
|
|
.methods()
|
|
.test_function()
|
|
.with_contract_ids(&[impl_contract_id])
|
|
.call()
|
|
.await
|
|
.unwrap();
|
|
assert!(res.value);
|
|
|
|
update_main_sw(tmp_dir.path()).unwrap();
|
|
let target = NodeTarget {
|
|
node_url: Some(node_url.clone()),
|
|
target: None,
|
|
testnet: false,
|
|
mainnet: false,
|
|
devnet: false,
|
|
};
|
|
let pkg = Pkg {
|
|
path: Some(tmp_dir.path().display().to_string()),
|
|
..Default::default()
|
|
};
|
|
|
|
let cmd = cmd::Deploy {
|
|
pkg,
|
|
salt: Some(vec![format!("{}", Salt::default())]),
|
|
node: target,
|
|
default_signer: true,
|
|
..Default::default()
|
|
};
|
|
let deployed_contract = expect_deployed_contract(deploy(cmd).await.unwrap().remove(0));
|
|
// proxy contract id should be the same.
|
|
let proxy_contract_after_update = deployed_contract.proxy.unwrap();
|
|
assert_eq!(proxy_contract_id, proxy_contract_after_update);
|
|
let impl_contract_id_after_update = deployed_contract.id;
|
|
assert!(impl_contract_id != impl_contract_id_after_update);
|
|
let impl_contract_a = ImplementationContract::new(proxy_contract_after_update, wallet_unlocked);
|
|
|
|
// Test storage functions
|
|
let res = impl_contract_a
|
|
.methods()
|
|
.test_function_read()
|
|
.with_contract_ids(&[impl_contract_id_after_update])
|
|
.call()
|
|
.await
|
|
.unwrap();
|
|
// Storage should be preserved from the previous target contract.
|
|
assert_eq!(res.value, 8);
|
|
let res = impl_contract_a
|
|
.methods()
|
|
.test_function_write(9)
|
|
.with_contract_ids(&[impl_contract_id_after_update])
|
|
.call()
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(res.value, 9);
|
|
|
|
let res = impl_contract_a
|
|
.methods()
|
|
.test_function()
|
|
.with_contract_ids(&[impl_contract_id_after_update])
|
|
.call()
|
|
.await
|
|
.unwrap();
|
|
assert!(!res.value);
|
|
node.kill().unwrap();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_non_owner_fails_to_set_target() {
|
|
let (mut node, port) = run_node();
|
|
let tmp_dir = tempdir().unwrap();
|
|
let project_dir = test_data_path().join("standalone_contract");
|
|
copy_dir(&project_dir, tmp_dir.path()).unwrap();
|
|
patch_manifest_file_with_path_std(tmp_dir.path()).unwrap();
|
|
let proxy = Proxy {
|
|
enabled: true,
|
|
address: None,
|
|
};
|
|
patch_manifest_file_with_proxy_table(tmp_dir.path(), proxy).unwrap();
|
|
|
|
let pkg = Pkg {
|
|
path: Some(tmp_dir.path().display().to_string()),
|
|
..Default::default()
|
|
};
|
|
|
|
let node_url = format!("http://127.0.0.1:{}/v1/graphql", port);
|
|
let target = NodeTarget {
|
|
node_url: Some(node_url.clone()),
|
|
target: None,
|
|
testnet: false,
|
|
mainnet: false,
|
|
devnet: false,
|
|
};
|
|
let cmd = cmd::Deploy {
|
|
pkg,
|
|
salt: Some(vec![format!("{}", Salt::default())]),
|
|
node: target,
|
|
default_signer: true,
|
|
..Default::default()
|
|
};
|
|
let contract_id = expect_deployed_contract(deploy(cmd).await.unwrap().remove(0));
|
|
// Proxy contract's id.
|
|
let proxy_id = contract_id.proxy.unwrap();
|
|
|
|
// Create and fund an owner account and an attacker account.
|
|
let provider = Provider::connect(&node_url).await.unwrap();
|
|
let attacker_secret_key = SecretKey::random(&mut thread_rng());
|
|
let attacker_signer = PrivateKeySigner::new(attacker_secret_key);
|
|
let attacker_wallet = Wallet::new(attacker_signer.clone(), provider.clone());
|
|
|
|
let owner_secret_key =
|
|
SecretKey::from_str(forc_client::constants::DEFAULT_PRIVATE_KEY).unwrap();
|
|
let owner_signer = PrivateKeySigner::new(owner_secret_key);
|
|
let owner_wallet = Wallet::new(owner_signer, provider.clone());
|
|
let consensus_parameters = provider.consensus_parameters().await.unwrap();
|
|
let base_asset_id = consensus_parameters.base_asset_id();
|
|
|
|
// Fund attacker wallet so that it can try to make a set proxy target call.
|
|
owner_wallet
|
|
.transfer(
|
|
attacker_wallet.address(),
|
|
100000,
|
|
*base_asset_id,
|
|
TxPolicies::default(),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let dummy_contract_id_target = ContractId::default();
|
|
abigen!(Contract(name = "ProxyContract", abi = "{\"programType\":\"contract\",\"specVersion\":\"1.1\",\"encodingVersion\":\"1\",\"concreteTypes\":[{\"type\":\"()\",\"concreteTypeId\":\"2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d\"},{\"type\":\"enum standards::src5::AccessError\",\"concreteTypeId\":\"3f702ea3351c9c1ece2b84048006c8034a24cbc2bad2e740d0412b4172951d3d\",\"metadataTypeId\":1},{\"type\":\"enum standards::src5::State\",\"concreteTypeId\":\"192bc7098e2fe60635a9918afb563e4e5419d386da2bdbf0d716b4bc8549802c\",\"metadataTypeId\":2},{\"type\":\"enum std::option::Option<struct std::contract_id::ContractId>\",\"concreteTypeId\":\"0d79387ad3bacdc3b7aad9da3a96f4ce60d9a1b6002df254069ad95a3931d5c8\",\"metadataTypeId\":4,\"typeArguments\":[\"29c10735d33b5159f0c71ee1dbd17b36a3e69e41f00fab0d42e1bd9f428d8a54\"]},{\"type\":\"enum sway_libs::ownership::errors::InitializationError\",\"concreteTypeId\":\"1dfe7feadc1d9667a4351761230f948744068a090fe91b1bc6763a90ed5d3893\",\"metadataTypeId\":5},{\"type\":\"enum sway_libs::upgradability::errors::SetProxyOwnerError\",\"concreteTypeId\":\"3c6e90ae504df6aad8b34a93ba77dc62623e00b777eecacfa034a8ac6e890c74\",\"metadataTypeId\":6},{\"type\":\"str\",\"concreteTypeId\":\"8c25cb3686462e9a86d2883c5688a22fe738b0bbc85f458d2d2b5f3f667c6d5a\"},{\"type\":\"struct std::contract_id::ContractId\",\"concreteTypeId\":\"29c10735d33b5159f0c71ee1dbd17b36a3e69e41f00fab0d42e1bd9f428d8a54\",\"metadataTypeId\":9},{\"type\":\"struct sway_libs::upgradability::events::ProxyOwnerSet\",\"concreteTypeId\":\"96dd838b44f99d8ccae2a7948137ab6256c48ca4abc6168abc880de07fba7247\",\"metadataTypeId\":10},{\"type\":\"struct sway_libs::upgradability::events::ProxyTargetSet\",\"concreteTypeId\":\"1ddc0adda1270a016c08ffd614f29f599b4725407c8954c8b960bdf651a9a6c8\",\"metadataTypeId\":11}],\"metadataTypes\":[{\"type\":\"b256\",\"metadataTypeId\":0},{\"type\":\"enum standards::src5::AccessError\",\"metadataTypeId\":1,\"components\":[{\"name\":\"NotOwner\",\"typeId\":\"2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d\"}]},{\"type\":\"enum standards::src5::State\",\"metadataTypeId\":2,\"components\":[{\"name\":\"Uninitialized\",\"typeId\":\"2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d\"},{\"name\":\"Initialized\",\"typeId\":3},{\"name\":\"Revoked\",\"typeId\":\"2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d\"}]},{\"type\":\"enum std::identity::Identity\",\"metadataTypeId\":3,\"components\":[{\"name\":\"Address\",\"typeId\":8},{\"name\":\"ContractId\",\"typeId\":9}]},{\"type\":\"enum std::option::Option\",\"metadataTypeId\":4,\"components\":[{\"name\":\"None\",\"typeId\":\"2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d\"},{\"name\":\"Some\",\"typeId\":7}],\"typeParameters\":[7]},{\"type\":\"enum sway_libs::ownership::errors::InitializationError\",\"metadataTypeId\":5,\"components\":[{\"name\":\"CannotReinitialized\",\"typeId\":\"2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d\"}]},{\"type\":\"enum sway_libs::upgradability::errors::SetProxyOwnerError\",\"metadataTypeId\":6,\"components\":[{\"name\":\"CannotUninitialize\",\"typeId\":\"2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d\"}]},{\"type\":\"generic T\",\"metadataTypeId\":7},{\"type\":\"struct std::address::Address\",\"metadataTypeId\":8,\"components\":[{\"name\":\"bits\",\"typeId\":0}]},{\"type\":\"struct std::contract_id::ContractId\",\"metadataTypeId\":9,\"components\":[{\"name\":\"bits\",\"typeId\":0}]},{\"type\":\"struct sway_libs::upgradability::events::ProxyOwnerSet\",\"metadataTypeId\":10,\"components\":[{\"name\":\"new_proxy_owner\",\"typeId\":2}]},{\"type\":\"struct sway_libs::upgradability::events::ProxyTargetSet\",\"metadataTypeId\":11,\"components\":[{\"name\":\"new_target\",\"typeId\":9}]}],\"functions\":[{\"inputs\":[],\"name\":\"proxy_target\",\"output\":\"0d79387ad3bacdc3b7aad9da3a96f4ce60d9a1b6002df254069ad95a3931d5c8\",\"attributes\":[{\"name\":\"doc-comment\",\"arguments\":[\" Returns the target contract of the proxy contract.\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Returns\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * [Option<ContractId>] - The new proxy contract to which all fallback calls will be passed or `None`.\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Number of Storage Accesses\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * Reads: `1`\"]},{\"name\":\"storage\",\"arguments\":[\"read\"]}]},{\"inputs\":[{\"name\":\"new_target\",\"concreteTypeId\":\"29c10735d33b5159f0c71ee1dbd17b36a3e69e41f00fab0d42e1bd9f428d8a54\"}],\"name\":\"set_proxy_target\",\"output\":\"2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d\",\"attributes\":[{\"name\":\"doc-comment\",\"arguments\":[\" Change the target contract of the proxy contract.\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Additional Information\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" This method can only be called by the `proxy_owner`.\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Arguments\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * `new_target`: [ContractId] - The new proxy contract to which all fallback calls will be passed.\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Reverts\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * When not called by `proxy_owner`.\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Number of Storage Accesses\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * Reads: `1`\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * Write: `1`\"]},{\"name\":\"storage\",\"arguments\":[\"read\",\"write\"]}]},{\"inputs\":[],\"name\":\"proxy_owner\",\"output\":\"192bc7098e2fe60635a9918afb563e4e5419d386da2bdbf0d716b4bc8549802c\",\"attributes\":[{\"name\":\"doc-comment\",\"arguments\":[\" Returns the owner of the proxy contract.\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Returns\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * [State] - Represents the state of ownership for this contract.\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Number of Storage Accesses\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * Reads: `1`\"]},{\"name\":\"storage\",\"arguments\":[\"read\"]}]},{\"inputs\":[],\"name\":\"initialize_proxy\",\"output\":\"2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d\",\"attributes\":[{\"name\":\"doc-comment\",\"arguments\":[\" Initializes the proxy contract.\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Additional Information\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" This method sets the storage values using the values of the configurable constants `INITIAL_TARGET` and `INITIAL_OWNER`.\"]},{\"name\":\"doc-comment\",\"arguments\":[\" This then allows methods that write to storage to be called.\"]},{\"name\":\"doc-comment\",\"arguments\":[\" This method can only be called once.\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Reverts\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * When `storage::SRC14.proxy_owner` is not [State::Uninitialized].\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Number of Storage Accesses\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * Writes: `2`\"]},{\"name\":\"storage\",\"arguments\":[\"write\"]}]},{\"inputs\":[{\"name\":\"new_proxy_owner\",\"concreteTypeId\":\"192bc7098e2fe60635a9918afb563e4e5419d386da2bdbf0d716b4bc8549802c\"}],\"name\":\"set_proxy_owner\",\"output\":\"2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d\",\"attributes\":[{\"name\":\"doc-comment\",\"arguments\":[\" Changes proxy ownership to the passed State.\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Additional Information\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" This method can be used to transfer ownership between Identities or to revoke ownership.\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Arguments\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * `new_proxy_owner`: [State] - The new state of the proxy ownership.\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Reverts\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * When the sender is not the current proxy owner.\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * When the new state of the proxy ownership is [State::Uninitialized].\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" # Number of Storage Accesses\"]},{\"name\":\"doc-comment\",\"arguments\":[\"\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * Reads: `1`\"]},{\"name\":\"doc-comment\",\"arguments\":[\" * Writes: `1`\"]},{\"name\":\"storage\",\"arguments\":[\"write\"]}]}],\"loggedTypes\":[{\"logId\":\"4571204900286667806\",\"concreteTypeId\":\"3f702ea3351c9c1ece2b84048006c8034a24cbc2bad2e740d0412b4172951d3d\"},{\"logId\":\"2151606668983994881\",\"concreteTypeId\":\"1ddc0adda1270a016c08ffd614f29f599b4725407c8954c8b960bdf651a9a6c8\"},{\"logId\":\"2161305517876418151\",\"concreteTypeId\":\"1dfe7feadc1d9667a4351761230f948744068a090fe91b1bc6763a90ed5d3893\"},{\"logId\":\"4354576968059844266\",\"concreteTypeId\":\"3c6e90ae504df6aad8b34a93ba77dc62623e00b777eecacfa034a8ac6e890c74\"},{\"logId\":\"10870989709723147660\",\"concreteTypeId\":\"96dd838b44f99d8ccae2a7948137ab6256c48ca4abc6168abc880de07fba7247\"},{\"logId\":\"10098701174489624218\",\"concreteTypeId\":\"8c25cb3686462e9a86d2883c5688a22fe738b0bbc85f458d2d2b5f3f667c6d5a\"}],\"messagesTypes\":[],\"configurables\":[{\"name\":\"INITIAL_TARGET\",\"concreteTypeId\":\"0d79387ad3bacdc3b7aad9da3a96f4ce60d9a1b6002df254069ad95a3931d5c8\",\"offset\":13368},{\"name\":\"INITIAL_OWNER\",\"concreteTypeId\":\"192bc7098e2fe60635a9918afb563e4e5419d386da2bdbf0d716b4bc8549802c\",\"offset\":13320}]}",));
|
|
|
|
let wallet = Wallet::new(attacker_signer, provider.clone());
|
|
let attacker_account = ForcClientAccount::Wallet(wallet);
|
|
// Try to change target of the proxy with a random wallet which is not the owner of the proxy.
|
|
let res = update_proxy_contract_target(&attacker_account, proxy_id, dummy_contract_id_target)
|
|
.await
|
|
.err()
|
|
.unwrap();
|
|
|
|
node.kill().unwrap();
|
|
assert!(res
|
|
.to_string()
|
|
.starts_with("transaction reverted: NotOwner"));
|
|
}
|
|
|
|
// TODO: https://github.com/FuelLabs/sway/issues/6283
|
|
// Add interactive tests for the happy path cases. This requires starting the node with funded accounts and setting up
|
|
// the wallet with the correct password. The tests should be run in a separate test suite that is not run by default.
|
|
// It would also require overriding `default_wallet_path` function for tests, so as not to interfere with the user's wallet.
|
|
|
|
#[test]
|
|
fn test_deploy_interactive_missing_wallet() -> Result<(), rexpect::error::Error> {
|
|
let (mut node, port) = run_node();
|
|
let node_url = format!("http://127.0.0.1:{}/v1/graphql", port);
|
|
|
|
// Spawn the forc-deploy binary using cargo run
|
|
let project_dir = test_data_path().join("standalone_contract");
|
|
let mut process = spawn(
|
|
&format!(
|
|
"cargo run --bin forc-deploy -- --node-url {node_url} -p {}",
|
|
project_dir.display()
|
|
),
|
|
Some(300000),
|
|
)?;
|
|
|
|
// Confirmation prompts
|
|
process
|
|
.exp_string("\u{1b}[1;32mConfirming\u{1b}[0m transactions [deploy standalone_contract]")?;
|
|
process.exp_string(&format!("Network: {node_url}"))?;
|
|
process.exp_regex("Could not find a wallet at")?;
|
|
process.send_line("n")?;
|
|
|
|
process.process.exit()?;
|
|
node.kill().unwrap();
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn chunked_deploy() {
|
|
let (mut node, port) = run_node();
|
|
let tmp_dir = tempdir().unwrap();
|
|
let project_dir = test_data_path().join("big_contract");
|
|
copy_dir(&project_dir, tmp_dir.path()).unwrap();
|
|
patch_manifest_file_with_path_std(tmp_dir.path()).unwrap();
|
|
|
|
let pkg = Pkg {
|
|
path: Some(tmp_dir.path().display().to_string()),
|
|
..Default::default()
|
|
};
|
|
|
|
let node_url = format!("http://127.0.0.1:{}/v1/graphql", port);
|
|
let target = NodeTarget {
|
|
node_url: Some(node_url),
|
|
target: None,
|
|
testnet: false,
|
|
mainnet: false,
|
|
devnet: false,
|
|
};
|
|
let cmd = cmd::Deploy {
|
|
pkg,
|
|
salt: Some(vec![format!("{}", Salt::default())]),
|
|
node: target,
|
|
default_signer: true,
|
|
..Default::default()
|
|
};
|
|
let deployed_contract = expect_deployed_contract(deploy(cmd).await.unwrap().remove(0));
|
|
node.kill().unwrap();
|
|
|
|
assert!(deployed_contract.chunked);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn chunked_deploy_re_routes_calls() {
|
|
let (mut node, port) = run_node();
|
|
let tmp_dir = tempdir().unwrap();
|
|
let project_dir = test_data_path().join("big_contract");
|
|
copy_dir(&project_dir, tmp_dir.path()).unwrap();
|
|
patch_manifest_file_with_path_std(tmp_dir.path()).unwrap();
|
|
|
|
let pkg = Pkg {
|
|
path: Some(tmp_dir.path().display().to_string()),
|
|
..Default::default()
|
|
};
|
|
|
|
let node_url = format!("http://127.0.0.1:{}/v1/graphql", port);
|
|
let target = NodeTarget {
|
|
node_url: Some(node_url.clone()),
|
|
target: None,
|
|
testnet: false,
|
|
mainnet: false,
|
|
devnet: false,
|
|
};
|
|
let cmd = cmd::Deploy {
|
|
pkg,
|
|
salt: Some(vec![format!("{}", Salt::default())]),
|
|
node: target,
|
|
default_signer: true,
|
|
..Default::default()
|
|
};
|
|
let deployed_contract = expect_deployed_contract(deploy(cmd).await.unwrap().remove(0));
|
|
|
|
let provider = Provider::connect(&node_url).await.unwrap();
|
|
let secret_key = SecretKey::from_str(forc_client::constants::DEFAULT_PRIVATE_KEY).unwrap();
|
|
let signer = PrivateKeySigner::new(secret_key);
|
|
let wallet_unlocked = Wallet::new(signer, provider);
|
|
|
|
assert_big_contract_calls(wallet_unlocked, deployed_contract.id).await;
|
|
|
|
node.kill().unwrap();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn chunked_deploy_with_proxy_re_routes_call() {
|
|
let (mut node, port) = run_node();
|
|
let tmp_dir = tempdir().unwrap();
|
|
let project_dir = test_data_path().join("big_contract");
|
|
copy_dir(&project_dir, tmp_dir.path()).unwrap();
|
|
patch_manifest_file_with_path_std(tmp_dir.path()).unwrap();
|
|
let proxy = Proxy {
|
|
enabled: true,
|
|
address: None,
|
|
};
|
|
patch_manifest_file_with_proxy_table(tmp_dir.path(), proxy).unwrap();
|
|
|
|
let pkg = Pkg {
|
|
path: Some(tmp_dir.path().display().to_string()),
|
|
..Default::default()
|
|
};
|
|
|
|
let node_url = format!("http://127.0.0.1:{}/v1/graphql", port);
|
|
let target = NodeTarget {
|
|
node_url: Some(node_url.clone()),
|
|
target: None,
|
|
testnet: false,
|
|
mainnet: false,
|
|
devnet: false,
|
|
};
|
|
let cmd = cmd::Deploy {
|
|
pkg,
|
|
salt: Some(vec![format!("{}", Salt::default())]),
|
|
node: target,
|
|
default_signer: true,
|
|
..Default::default()
|
|
};
|
|
let deployed_contract = expect_deployed_contract(deploy(cmd).await.unwrap().remove(0));
|
|
|
|
let provider = Provider::connect(&node_url).await.unwrap();
|
|
let secret_key = SecretKey::from_str(forc_client::constants::DEFAULT_PRIVATE_KEY).unwrap();
|
|
let signer = PrivateKeySigner::new(secret_key);
|
|
let wallet_unlocked = Wallet::new(signer, provider);
|
|
|
|
assert_big_contract_calls(wallet_unlocked, deployed_contract.id).await;
|
|
|
|
node.kill().unwrap();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn can_deploy_script() {
|
|
let (mut node, port) = run_node();
|
|
let tmp_dir = tempdir().unwrap();
|
|
let project_dir = test_data_path().join("deployed_script");
|
|
copy_dir(&project_dir, tmp_dir.path()).unwrap();
|
|
patch_manifest_file_with_path_std(tmp_dir.path()).unwrap();
|
|
|
|
let node_url = format!("http://127.0.0.1:{}/v1/graphql", port);
|
|
let target = NodeTarget {
|
|
node_url: Some(node_url.clone()),
|
|
target: None,
|
|
testnet: false,
|
|
mainnet: false,
|
|
devnet: false,
|
|
};
|
|
let pkg = Pkg {
|
|
path: Some(tmp_dir.path().display().to_string()),
|
|
..Default::default()
|
|
};
|
|
let cmd = cmd::Deploy {
|
|
pkg,
|
|
salt: Some(vec![format!("{}", Salt::default())]),
|
|
node: target,
|
|
default_signer: true,
|
|
..Default::default()
|
|
};
|
|
|
|
expect_deployed_script(deploy(cmd).await.unwrap().remove(0));
|
|
node.kill().unwrap();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn deploy_script_calls() {
|
|
let (mut node, port) = run_node();
|
|
let tmp_dir = tempdir().unwrap();
|
|
let project_dir = test_data_path().join("deployed_script");
|
|
copy_dir(&project_dir, tmp_dir.path()).unwrap();
|
|
patch_manifest_file_with_path_std(tmp_dir.path()).unwrap();
|
|
|
|
let node_url = format!("http://127.0.0.1:{}/v1/graphql", port);
|
|
let target = NodeTarget {
|
|
node_url: Some(node_url.clone()),
|
|
target: None,
|
|
testnet: false,
|
|
mainnet: false,
|
|
devnet: false,
|
|
};
|
|
let pkg = Pkg {
|
|
path: Some(tmp_dir.path().display().to_string()),
|
|
..Default::default()
|
|
};
|
|
let cmd = cmd::Deploy {
|
|
pkg,
|
|
salt: Some(vec![format!("{}", Salt::default())]),
|
|
node: target,
|
|
default_signer: true,
|
|
..Default::default()
|
|
};
|
|
|
|
expect_deployed_script(deploy(cmd).await.unwrap().remove(0));
|
|
|
|
// Deploy the contract the script is going to be calling.
|
|
let contract_tmp_dir = tempdir().unwrap();
|
|
let project_dir = test_data_path().join("standalone_contract");
|
|
copy_dir(&project_dir, contract_tmp_dir.path()).unwrap();
|
|
patch_manifest_file_with_path_std(contract_tmp_dir.path()).unwrap();
|
|
|
|
let pkg = Pkg {
|
|
path: Some(contract_tmp_dir.path().display().to_string()),
|
|
..Default::default()
|
|
};
|
|
|
|
let node_url = format!("http://127.0.0.1:{}/v1/graphql", port);
|
|
let target = NodeTarget {
|
|
node_url: Some(node_url.clone()),
|
|
target: None,
|
|
testnet: false,
|
|
mainnet: false,
|
|
devnet: false,
|
|
};
|
|
let cmd = cmd::Deploy {
|
|
pkg,
|
|
salt: Some(vec![format!("{}", Salt::default())]),
|
|
node: target,
|
|
default_signer: true,
|
|
..Default::default()
|
|
};
|
|
let deployed_packages = deploy(cmd).await.unwrap().remove(0);
|
|
let contract = expect_deployed_contract(deployed_packages);
|
|
let contract_id = contract.id;
|
|
|
|
abigen!(Script(
|
|
name = "MyScript",
|
|
abi = "forc-plugins/forc-client/test/data/deployed_script/deployed_script-abi.json"
|
|
));
|
|
|
|
let provider = Provider::connect(&node_url).await.unwrap();
|
|
let secret_key = SecretKey::from_str(forc_client::constants::DEFAULT_PRIVATE_KEY).unwrap();
|
|
let signer = PrivateKeySigner::new(secret_key);
|
|
let wallet_unlocked = Wallet::new(signer, provider);
|
|
|
|
let loader_path = tmp_dir.path().join("out/deployed_script-loader.bin");
|
|
let instance = MyScript::new(wallet_unlocked, &loader_path.display().to_string());
|
|
|
|
let contract_id_bits256 = Bits256(contract.id.into());
|
|
let call_handler = instance
|
|
.main(10, contract_id_bits256)
|
|
.with_contract_ids(&[contract_id])
|
|
.call()
|
|
.await
|
|
.unwrap();
|
|
let (configs, with_input, without_input, from_contract) = call_handler.value;
|
|
let receipts = call_handler.tx_status.receipts;
|
|
|
|
assert!(configs.0); // bool
|
|
assert_eq!(configs.1, 8); // u8
|
|
assert_eq!(configs.2, 16); // u16
|
|
assert_eq!(configs.3, 32); // u32
|
|
assert_eq!(configs.4, 63); // u64
|
|
assert_eq!(configs.5, 8.into()); // u256
|
|
assert_eq!(
|
|
configs.6,
|
|
Bits256::from_hex_str("0x0101010101010101010101010101010101010101010101010101010101010101")
|
|
.unwrap()
|
|
); // b256
|
|
assert_eq!(
|
|
configs.7,
|
|
SizedAsciiString::new("fuel".to_string()).unwrap()
|
|
); // str[4]
|
|
assert_eq!(configs.8, (8, true)); // tuple
|
|
assert_eq!(configs.9, [253, 254, 255]); // array
|
|
|
|
let expected_struct = StructWithGeneric {
|
|
field_1: 8,
|
|
field_2: 16,
|
|
};
|
|
assert_eq!(configs.10, expected_struct); // struct
|
|
|
|
let expected_enum = EnumWithGeneric::VariantOne(true);
|
|
assert_eq!(configs.11, expected_enum); // enum
|
|
|
|
assert!(with_input); // 10 % 2 == 0
|
|
assert_eq!(without_input, 2500); // 25 * 100 = 2500
|
|
|
|
assert_eq!(from_contract, 5);
|
|
|
|
receipts.iter().find(|receipt| {
|
|
if let fuel_tx::Receipt::LogData { data, .. } = receipt {
|
|
data == &Some(vec![0x08])
|
|
} else {
|
|
false
|
|
}
|
|
});
|
|
|
|
node.kill().unwrap();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn can_deploy_predicates() {
|
|
let (mut node, port) = run_node();
|
|
let tmp_dir = tempdir().unwrap();
|
|
let project_dir = test_data_path().join("deployed_predicate");
|
|
copy_dir(&project_dir, tmp_dir.path()).unwrap();
|
|
patch_manifest_file_with_path_std(tmp_dir.path()).unwrap();
|
|
|
|
let node_url = format!("http://127.0.0.1:{}/v1/graphql", port);
|
|
let target = NodeTarget {
|
|
node_url: Some(node_url.clone()),
|
|
target: None,
|
|
testnet: false,
|
|
mainnet: false,
|
|
devnet: false,
|
|
};
|
|
let pkg = Pkg {
|
|
path: Some(tmp_dir.path().display().to_string()),
|
|
..Default::default()
|
|
};
|
|
let cmd = cmd::Deploy {
|
|
pkg,
|
|
salt: Some(vec![format!("{}", Salt::default())]),
|
|
node: target,
|
|
default_signer: true,
|
|
..Default::default()
|
|
};
|
|
|
|
expect_deployed_predicate(deploy(cmd).await.unwrap().remove(0));
|
|
node.kill().unwrap();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn deployed_predicate_call() {
|
|
let (mut node, port) = run_node();
|
|
let tmp_dir = tempdir().unwrap();
|
|
let project_dir = test_data_path().join("deployed_predicate");
|
|
copy_dir(&project_dir, tmp_dir.path()).unwrap();
|
|
patch_manifest_file_with_path_std(tmp_dir.path()).unwrap();
|
|
|
|
let node_url = format!("http://127.0.0.1:{}/v1/graphql", port);
|
|
let target = NodeTarget {
|
|
node_url: Some(node_url.clone()),
|
|
target: None,
|
|
testnet: false,
|
|
mainnet: false,
|
|
devnet: false,
|
|
};
|
|
let pkg = Pkg {
|
|
path: Some(tmp_dir.path().display().to_string()),
|
|
..Default::default()
|
|
};
|
|
let cmd = cmd::Deploy {
|
|
pkg,
|
|
salt: Some(vec![format!("{}", Salt::default())]),
|
|
node: target,
|
|
default_signer: true,
|
|
..Default::default()
|
|
};
|
|
expect_deployed_predicate(deploy(cmd).await.unwrap().remove(0));
|
|
|
|
abigen!(Predicate(
|
|
name = "MyPredicate",
|
|
abi = "forc-plugins/forc-client/test/data/deployed_predicate/deployed_predicate-abi.json"
|
|
));
|
|
|
|
let provider = Provider::connect(&node_url).await.unwrap();
|
|
let consensus_parameters = provider.consensus_parameters().await.unwrap();
|
|
let base_asset_id = consensus_parameters.base_asset_id();
|
|
let secret_key = SecretKey::from_str(forc_client::constants::DEFAULT_PRIVATE_KEY).unwrap();
|
|
let signer = PrivateKeySigner::new(secret_key);
|
|
let wallet_unlocked = Wallet::new(signer, provider.clone());
|
|
let loader_path = tmp_dir.path().join("out/deployed_predicate-loader.bin");
|
|
let strct = StructWithGeneric {
|
|
field_1: 8,
|
|
field_2: 16,
|
|
};
|
|
let enm = EnumWithGeneric::VariantOne(true);
|
|
let encoded_data = MyPredicateEncoder::default()
|
|
.encode_data(true, 8, strct, enm)
|
|
.unwrap();
|
|
let predicate: fuels::prelude::Predicate =
|
|
fuels::prelude::Predicate::load_from(&loader_path.display().to_string())
|
|
.unwrap()
|
|
.with_data(encoded_data)
|
|
.with_provider(provider);
|
|
|
|
// lock some amount under the predicate
|
|
wallet_unlocked
|
|
.transfer(
|
|
predicate.address(),
|
|
2000,
|
|
*base_asset_id,
|
|
TxPolicies::default(),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Check predicate balance.
|
|
let balance = predicate.get_asset_balance(base_asset_id).await.unwrap();
|
|
assert_eq!(balance, 2000);
|
|
|
|
// Try to spend it
|
|
let amount_to_unlock = 300;
|
|
predicate
|
|
.transfer(
|
|
wallet_unlocked.address(),
|
|
amount_to_unlock,
|
|
*base_asset_id,
|
|
TxPolicies::default(),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Check predicate balance again.
|
|
let balance = predicate.get_asset_balance(base_asset_id).await.unwrap();
|
|
assert_eq!(balance, 828);
|
|
|
|
node.kill().unwrap();
|
|
}
|
|
|
|
/// Generates a script instance using SDK, and returns the result as a string.
|
|
async fn call_with_sdk_generated_overrides(node_url: &str, contract_id: ContractId) -> String {
|
|
let project_dir = test_data_path().join("deployed_script");
|
|
abigen!(Script(
|
|
name = "MyScript",
|
|
abi = "forc-plugins/forc-client/test/data/deployed_script/deployed_script-abi.json"
|
|
));
|
|
let provider = Provider::connect(&node_url).await.unwrap();
|
|
let secret_key = SecretKey::from_str(forc_client::constants::DEFAULT_PRIVATE_KEY).unwrap();
|
|
let signer = PrivateKeySigner::new(secret_key);
|
|
let wallet_unlocked = Wallet::new(signer, provider);
|
|
let bin_dir = project_dir.join("deployed_script.bin");
|
|
let script_instance = MyScript::new(wallet_unlocked, bin_dir.display().to_string().as_str());
|
|
|
|
let strc = StructWithGeneric {
|
|
field_1: 1u8,
|
|
field_2: 2,
|
|
};
|
|
let encoded = MyScriptConfigurables::default()
|
|
.with_BOOL(false)
|
|
.unwrap()
|
|
.with_U8(1)
|
|
.unwrap()
|
|
.with_U16(2)
|
|
.unwrap()
|
|
.with_U32(3)
|
|
.unwrap()
|
|
.with_U64(4)
|
|
.unwrap()
|
|
.with_U256(5.into())
|
|
.unwrap()
|
|
.with_B256(Bits256::zeroed())
|
|
.unwrap()
|
|
.with_ARRAY([1, 2, 3])
|
|
.unwrap()
|
|
.with_STRUCT(strc)
|
|
.unwrap()
|
|
.with_ENUM(EnumWithGeneric::VariantTwo)
|
|
.unwrap();
|
|
|
|
let mut script_instance_with_configs = script_instance.with_configurables(encoded);
|
|
|
|
let loader_from_sdk = script_instance_with_configs
|
|
.convert_into_loader()
|
|
.await
|
|
.unwrap();
|
|
|
|
let contract_ids_bits256 = Bits256(contract_id.into());
|
|
format!(
|
|
"{:?}",
|
|
loader_from_sdk
|
|
.main(10, contract_ids_bits256)
|
|
.with_contract_ids(&[contract_id])
|
|
.call()
|
|
.await
|
|
.unwrap()
|
|
.value
|
|
)
|
|
}
|
|
|
|
/// Generates a script instance using the shifted abi, and returns the result as a string.
|
|
async fn call_with_forc_generated_overrides(node_url: &str, contract_id: ContractId) -> String {
|
|
let provider = Provider::connect(&node_url).await.unwrap();
|
|
let secret_key = SecretKey::from_str(forc_client::constants::DEFAULT_PRIVATE_KEY).unwrap();
|
|
let signer = PrivateKeySigner::new(secret_key);
|
|
let wallet_unlocked = Wallet::new(signer, provider);
|
|
let tmp_dir = tempdir().unwrap();
|
|
let project_dir = test_data_path().join("deployed_script");
|
|
copy_dir(&project_dir, tmp_dir.path()).unwrap();
|
|
patch_manifest_file_with_path_std(tmp_dir.path()).unwrap();
|
|
|
|
let target = NodeTarget {
|
|
node_url: Some(node_url.to_string()),
|
|
target: None,
|
|
testnet: false,
|
|
mainnet: false,
|
|
devnet: false,
|
|
};
|
|
let pkg = Pkg {
|
|
path: Some(tmp_dir.path().display().to_string()),
|
|
..Default::default()
|
|
};
|
|
let cmd = cmd::Deploy {
|
|
pkg,
|
|
salt: Some(vec![format!("{}", Salt::default())]),
|
|
node: target,
|
|
default_signer: true,
|
|
..Default::default()
|
|
};
|
|
|
|
expect_deployed_script(deploy(cmd).await.unwrap().remove(0));
|
|
|
|
// Since `abigen!` macro does not allow for dynamic paths, we need to
|
|
// pre-generate the loader bin and abi and read them from project dir. Here
|
|
// we are ensuring forc-deploy indeed generated the files we are basing our
|
|
// tests below.
|
|
let generated_loader_abi_path = tmp_dir.path().join("out/deployed_script-loader-abi.json");
|
|
let generated_loader_abi = fs::read_to_string(generated_loader_abi_path).unwrap();
|
|
|
|
// this path is basically, `forc-plugins/forc-client/test/data/deployed_script/deployed_script-loader-abi.json`.
|
|
let used_loader_abi_path = project_dir.join("deployed_script-loader-abi.json");
|
|
let used_loader_abi = fs::read_to_string(used_loader_abi_path).unwrap();
|
|
|
|
assert_eq!(generated_loader_abi, used_loader_abi);
|
|
|
|
let generated_loader_bin = tmp_dir.path().join("out/deployed_script-loader.bin");
|
|
abigen!(Script(
|
|
name = "MyScript",
|
|
abi = "forc-plugins/forc-client/test/data/deployed_script/deployed_script-loader-abi.json"
|
|
));
|
|
let forc_generated_script_instance = MyScript::new(
|
|
wallet_unlocked,
|
|
generated_loader_bin.display().to_string().as_str(),
|
|
);
|
|
let strc = StructWithGeneric {
|
|
field_1: 1u8,
|
|
field_2: 2,
|
|
};
|
|
let encoded = MyScriptConfigurables::default()
|
|
.with_BOOL(false)
|
|
.unwrap()
|
|
.with_U8(1)
|
|
.unwrap()
|
|
.with_U16(2)
|
|
.unwrap()
|
|
.with_U32(3)
|
|
.unwrap()
|
|
.with_U64(4)
|
|
.unwrap()
|
|
.with_U256(5.into())
|
|
.unwrap()
|
|
.with_B256(Bits256::zeroed())
|
|
.unwrap()
|
|
.with_ARRAY([1, 2, 3])
|
|
.unwrap()
|
|
.with_STRUCT(strc)
|
|
.unwrap()
|
|
.with_ENUM(EnumWithGeneric::VariantTwo)
|
|
.unwrap();
|
|
|
|
let forc_generated_script_with_configs =
|
|
forc_generated_script_instance.with_configurables(encoded);
|
|
let contract_ids_bits256 = Bits256(contract_id.into());
|
|
format!(
|
|
"{:?}",
|
|
forc_generated_script_with_configs
|
|
.main(10, contract_ids_bits256)
|
|
.with_contract_ids(&[contract_id])
|
|
.call()
|
|
.await
|
|
.unwrap()
|
|
.value
|
|
)
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn offset_shifted_abi_works() {
|
|
// To test if offset shifted abi works or not, we generate a loader
|
|
// contract using sdk and give a configurable override, and call the
|
|
// main function.
|
|
|
|
// We also create the shifted abi using forc-deploy and create a script
|
|
// instance using this new shifted abi, and generate a normal script out of
|
|
// the loader binary generated again by forc-deploy.
|
|
|
|
// We then override the configurables with the same values as sdk flow on
|
|
// this script, generated with loader abi and bin coming from forc-deploy.
|
|
|
|
// If returned value is equal, than the configurables work correctly.
|
|
let (mut node, port) = run_node();
|
|
// Deploy the contract the script is going to be calling.
|
|
let contract_tmp_dir = tempdir().unwrap();
|
|
let project_dir = test_data_path().join("standalone_contract");
|
|
copy_dir(&project_dir, contract_tmp_dir.path()).unwrap();
|
|
patch_manifest_file_with_path_std(contract_tmp_dir.path()).unwrap();
|
|
|
|
let pkg = Pkg {
|
|
path: Some(contract_tmp_dir.path().display().to_string()),
|
|
..Default::default()
|
|
};
|
|
|
|
let node_url = format!("http://127.0.0.1:{}/v1/graphql", port);
|
|
let target = NodeTarget {
|
|
node_url: Some(node_url.clone()),
|
|
target: None,
|
|
testnet: false,
|
|
mainnet: false,
|
|
devnet: false,
|
|
};
|
|
let cmd = cmd::Deploy {
|
|
pkg,
|
|
salt: Some(vec![format!("{}", Salt::default())]),
|
|
node: target,
|
|
default_signer: true,
|
|
..Default::default()
|
|
};
|
|
let deployed_packages = deploy(cmd).await.unwrap().remove(0);
|
|
let contract = expect_deployed_contract(deployed_packages);
|
|
let contract_id = contract.id;
|
|
// Generating the sdk loader bytecode with configurables.
|
|
let loader_with_configs_from_sdk =
|
|
call_with_sdk_generated_overrides(&node_url, contract_id).await;
|
|
|
|
// Generating the forc-deploy loader bytecode and loader abi.
|
|
let loader_with_configs_from_forc =
|
|
call_with_forc_generated_overrides(&node_url, contract_id).await;
|
|
pretty_assertions::assert_eq!(loader_with_configs_from_forc, loader_with_configs_from_sdk);
|
|
|
|
node.kill().unwrap()
|
|
}
|