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

Add GMP precompile killswitch #2292

Merged
merged 12 commits into from
May 22, 2023
38 changes: 35 additions & 3 deletions precompiles/gmp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
#![cfg_attr(not(feature = "std"), no_std)]

use evm::ExitReason;
use fp_evm::{Context, PrecompileFailure, PrecompileHandle};
use fp_evm::{Context, ExitRevert, PrecompileFailure, PrecompileHandle};
use frame_support::{
codec::Decode,
dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo},
Expand Down Expand Up @@ -78,12 +78,14 @@ where
log::debug!(target: "gmp-precompile", "wormhole_vaa: {:?}", wormhole_vaa.clone());

// tally up gas cost:
// 1 read for enabled flag
// 2 reads for contract addresses
// 2500 as fudge for computation, esp. payload decoding (TODO: benchmark?)
let initial_gas = 2500 + 2 * RuntimeHelper::<Runtime>::db_read_gas_cost();
log::warn!("initial_gas: {:?}", initial_gas);
let initial_gas = 2500 + 3 * RuntimeHelper::<Runtime>::db_read_gas_cost();
handle.record_cost(initial_gas)?;

ensure_enabled()?;

let wormhole = storage::CoreAddress::get()
.ok_or(RevertReason::custom("invalid wormhole core address"))?;

Expand Down Expand Up @@ -242,11 +244,30 @@ fn ensure_exit_reason_success(reason: ExitReason, output: &[u8]) -> EvmResult<()
}
}

pub fn is_enabled() -> bool {
match storage::PrecompileEnabled::get() {
Some(enabled) => enabled,
_ => false,
}
}

fn ensure_enabled() -> EvmResult<()> {
if is_enabled() {
Ok(())
} else {
Err(PrecompileFailure::Revert {
exit_status: ExitRevert::Reverted,
output: b"GMP Precompile is not enabled".to_vec(),
})
}
}

/// We use pallet storage in our precompile by implementing a StorageInstance for each item we need
/// to store.
/// twox_128("gmp") => 0xb7f047395bba5df0367b45771c00de50
/// twox_128("CoreAddress") => 0x59ff23ff65cc809711800d9d04e4b14c
/// twox_128("BridgeAddress") => 0xc1586bde54b249fb7f521faf831ade45
/// twox_128("PrecompileEnabled") => 0x2551bba17abb82ef3498bab688e470b8
mod storage {
use super::*;
use frame_support::{
Expand All @@ -273,4 +294,15 @@ mod storage {
}
}
pub type BridgeAddress = StorageValue<BridgeAddressStorageInstance, H160, OptionQuery>;

// storage for precompile enabled
// None or Some(false) both mean that the precompile is disabled; only Some(true) means enabled.
pub struct PrecompileEnabledStorageInstance;
impl StorageInstance for PrecompileEnabledStorageInstance {
const STORAGE_PREFIX: &'static str = "PrecompileEnabled";
fn pallet_prefix() -> &'static str {
"gmp"
}
}
pub type PrecompileEnabled = StorageValue<PrecompileEnabledStorageInstance, bool, OptionQuery>;
}
38 changes: 38 additions & 0 deletions precompiles/gmp/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -385,3 +385,41 @@ impl orml_xtokens::Config for Runtime {
type ReserveProvider = AbsoluteReserveProvider;
type UniversalLocation = UniversalLocation;
}

pub(crate) struct ExtBuilder {
/// Endowed accounts with balances
balances: Vec<(AccountId, Balance)>,
}

impl Default for ExtBuilder {
fn default() -> ExtBuilder {
ExtBuilder { balances: vec![] }
}
}

impl ExtBuilder {
/// Fund some accounts before starting the test
pub(crate) fn with_balances(mut self, balances: Vec<(AccountId, Balance)>) -> Self {
self.balances = balances;
self
}

/// Build the test externalities for use in tests
pub(crate) fn build(self) -> sp_io::TestExternalities {
let mut t = frame_system::GenesisConfig::default()
.build_storage::<Runtime>()
.expect("Frame system builds valid default genesis config");

pallet_balances::GenesisConfig::<Runtime> {
balances: self.balances.clone(),
}
.assimilate_storage(&mut t)
.expect("Pallet balances storage can be assimilated");

let mut ext = sp_io::TestExternalities::new(t);
ext.execute_with(|| {
System::set_block_number(1);
});
ext
}
}
89 changes: 88 additions & 1 deletion precompiles/gmp/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,96 @@
// You should have received a copy of the GNU General Public License
// along with Moonbeam. If not, see <http://www.gnu.org/licenses/>.

use crate::mock::PCall;
use crate::mock::*;
use fp_evm::{ExitRevert, PrecompileFailure};
use precompile_utils::testing::*;

fn precompiles() -> Precompiles<Runtime> {
PrecompilesValue::get()
}

#[test]
fn contract_disabling_default_value_is_false() {
ExtBuilder::default()
.with_balances(vec![(Alice.into(), 100_000)])
.build()
.execute_with(|| {
// default should be false
assert_eq!(crate::storage::PrecompileEnabled::get(), None);
assert_eq!(crate::is_enabled(), false);
assert_eq!(
crate::ensure_enabled(),
Err(PrecompileFailure::Revert {
exit_status: ExitRevert::Reverted,
output: b"GMP Precompile is not enabled".to_vec(),
})
);

precompiles()
.prepare_test(
CryptoAlith,
Precompile1,
PCall::wormhole_transfer_erc20 {
wormhole_vaa: Vec::new().into(),
},
)
.execute_reverts_no_decode(|output| output == b"GMP Precompile is not enabled");
})
}

#[test]
fn contract_enabling_works() {
ExtBuilder::default()
.with_balances(vec![(Alice.into(), 100_000)])
.build()
.execute_with(|| {
crate::storage::PrecompileEnabled::set(Some(true));
assert_eq!(crate::storage::PrecompileEnabled::get(), Some(true));
assert_eq!(crate::is_enabled(), true);
assert_eq!(crate::ensure_enabled(), Ok(()));

// should fail at a later point since contract addresses are not set
precompiles()
.prepare_test(
CryptoAlith,
Precompile1,
PCall::wormhole_transfer_erc20 {
wormhole_vaa: Vec::new().into(),
},
)
.execute_reverts(|output| output == b"invalid wormhole core address");
})
}

#[test]
fn contract_disabling_works() {
ExtBuilder::default()
.with_balances(vec![(Alice.into(), 100_000)])
.build()
.execute_with(|| {
crate::storage::PrecompileEnabled::set(Some(false));
assert_eq!(crate::storage::PrecompileEnabled::get(), Some(false));
assert_eq!(crate::is_enabled(), false);
assert_eq!(
crate::ensure_enabled(),
Err(PrecompileFailure::Revert {
exit_status: ExitRevert::Reverted,
output: b"GMP Precompile is not enabled".to_vec(),
})
);

precompiles()
.prepare_test(
CryptoAlith,
Precompile1,
PCall::wormhole_transfer_erc20 {
wormhole_vaa: Vec::new().into(),
},
)
.execute_reverts_no_decode(|output| output == b"GMP Precompile is not enabled");
})
}

#[test]
fn test_solidity_interface_has_all_function_selectors_documented_and_implemented() {
check_precompile_implements_solidity_interfaces(&["Gmp.sol"], PCall::supports_selector)
Expand Down
26 changes: 26 additions & 0 deletions precompiles/utils/src/testing/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,32 @@ impl<'p, P: PrecompileSet> PrecompilesTester<'p, P> {
self.assert_optionals();
}

/// Execute the precompile set and check if it reverts.
/// Take a closure allowing to perform custom matching on the output.
/// Output is not automatically decoded.
pub fn execute_reverts_no_decode(mut self, check: impl Fn(&[u8]) -> bool) {
notlesh marked this conversation as resolved.
Show resolved Hide resolved
let res = self.execute();

match res {
Some(Err(PrecompileFailure::Revert { output, .. })) => {
if !check(&output) {
eprintln!(
"Revert message (bytes): {:?}",
sp_core::hexdisplay::HexDisplay::from(&output)
);
eprintln!(
"Revert message (string): {:?}",
core::str::from_utf8(&output).ok()
);
panic!("Revert reason doesn't match !");
}
}
other => panic!("Didn't revert, instead returned {:?}", other),
}

self.assert_optionals();
}

/// Execute the precompile set and check it returns provided output.
pub fn execute_error(mut self, error: ExitError) {
let res = self.execute();
Expand Down