mirror of
https://github.com/pezkuwichain/revive.git
synced 2026-10-26 05:35:06 +00:00
Fix optimization specific SREM edge case (#527)
Existing test coverage didn't catch a signed remainder overflow semantics edge case behavior where at least one the operators is being constant folded by LLVM instead of solc. This PR fixes the SREM translation bug and adds missing test coverage for division ops with constants. Closes #524 --------- Signed-off-by: xermicus <cyrill@parity.io>
This commit is contained in:
1 parent
62a62770e3
commit
40e97d11eb
23 files changed
+1336
-21
No files matched your search
@@ -1,11 +1,13 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use alloy_primitives::*;
|
||||
use alloy_sol_types::SolCall;
|
||||
use resolc::test_utils::build_yul;
|
||||
use revive_runner::*;
|
||||
use SpecsAction::*;
|
||||
|
||||
use crate::cases::Contract;
|
||||
use crate::cases::{DivConst, ModConst, SdivConst, SmodConst};
|
||||
|
||||
/// Parameters:
|
||||
/// - The function name of the test
|
||||
@@ -268,6 +270,522 @@ fn signed_remainder() {
|
||||
run_differential(actions);
|
||||
}
|
||||
|
||||
/// Build calldata for the both-const Yul fixtures. Each fixture reads the case
|
||||
/// index from `calldataload(0)` and a 32-byte "tag" from `calldataload(32)`
|
||||
/// that gets XORed into the returned result. A non-zero tag turns silent
|
||||
/// poison/undef from a UB-triggering const-fold into an observable divergence
|
||||
/// from EVM (otherwise the buggy path coincidentally returns 0, which matches
|
||||
/// EVM SMOD/SDIV's defined result for INT_MIN op -1 and masks the bug).
|
||||
fn yul_which_calldata(which: u8) -> Vec<u8> {
|
||||
let mut data = vec![0u8; 64];
|
||||
data[31] = which;
|
||||
// tag: 0xdeadbeef padded into the second 32-byte word
|
||||
data[60..64].copy_from_slice(&[0xde, 0xad, 0xbe, 0xef]);
|
||||
data
|
||||
}
|
||||
|
||||
fn instantiate_yul(path: &str, contract: &str) -> Vec<SpecsAction> {
|
||||
vec![Instantiate {
|
||||
origin: TestAddress::Alice,
|
||||
value: 0,
|
||||
gas_limit: Some(GAS_LIMIT),
|
||||
storage_deposit_limit: None,
|
||||
code: Code::Yul {
|
||||
path: path.into(),
|
||||
contract: contract.to_string(),
|
||||
},
|
||||
data: vec![],
|
||||
salt: OptionalHex::default(),
|
||||
}]
|
||||
}
|
||||
|
||||
fn unsigned_const_set() -> (U256, U256, U256, U256) {
|
||||
(U256::from(1), U256::from(2), U256::from(5), U256::MAX)
|
||||
}
|
||||
|
||||
fn signed_const_set() -> (I256, I256, I256, I256, I256, I256, I256, I256) {
|
||||
(
|
||||
I256::try_from(1).unwrap(),
|
||||
I256::try_from(2).unwrap(),
|
||||
I256::try_from(-2).unwrap(),
|
||||
I256::try_from(5).unwrap(),
|
||||
I256::try_from(-5).unwrap(),
|
||||
I256::MIN,
|
||||
I256::MIN + I256::ONE,
|
||||
I256::MAX,
|
||||
)
|
||||
}
|
||||
|
||||
fn div_rhs_const_data(n: U256, d: U256) -> Vec<u8> {
|
||||
let (one, two, five, max) = unsigned_const_set();
|
||||
if d == U256::ZERO {
|
||||
DivConst::divRhsZeroCall::new((n,)).abi_encode()
|
||||
} else if d == one {
|
||||
DivConst::divRhsOneCall::new((n,)).abi_encode()
|
||||
} else if d == two {
|
||||
DivConst::divRhsTwoCall::new((n,)).abi_encode()
|
||||
} else if d == five {
|
||||
DivConst::divRhsFiveCall::new((n,)).abi_encode()
|
||||
} else if d == max {
|
||||
DivConst::divRhsMaxCall::new((n,)).abi_encode()
|
||||
} else {
|
||||
panic!("no divRhsConst variant for d={d}")
|
||||
}
|
||||
}
|
||||
|
||||
fn div_lhs_const_data(n: U256, d: U256) -> Vec<u8> {
|
||||
let (one, two, five, max) = unsigned_const_set();
|
||||
if n == U256::ZERO {
|
||||
DivConst::divLhsZeroCall::new((d,)).abi_encode()
|
||||
} else if n == one {
|
||||
DivConst::divLhsOneCall::new((d,)).abi_encode()
|
||||
} else if n == two {
|
||||
DivConst::divLhsTwoCall::new((d,)).abi_encode()
|
||||
} else if n == five {
|
||||
DivConst::divLhsFiveCall::new((d,)).abi_encode()
|
||||
} else if n == max {
|
||||
DivConst::divLhsMaxCall::new((d,)).abi_encode()
|
||||
} else {
|
||||
panic!("no divLhsConst variant for n={n}")
|
||||
}
|
||||
}
|
||||
|
||||
fn mod_rhs_const_data(n: U256, d: U256) -> Vec<u8> {
|
||||
let (one, two, five, max) = unsigned_const_set();
|
||||
if d == U256::ZERO {
|
||||
ModConst::modRhsZeroCall::new((n,)).abi_encode()
|
||||
} else if d == one {
|
||||
ModConst::modRhsOneCall::new((n,)).abi_encode()
|
||||
} else if d == two {
|
||||
ModConst::modRhsTwoCall::new((n,)).abi_encode()
|
||||
} else if d == five {
|
||||
ModConst::modRhsFiveCall::new((n,)).abi_encode()
|
||||
} else if d == max {
|
||||
ModConst::modRhsMaxCall::new((n,)).abi_encode()
|
||||
} else {
|
||||
panic!("no modRhsConst variant for d={d}")
|
||||
}
|
||||
}
|
||||
|
||||
fn mod_lhs_const_data(n: U256, d: U256) -> Vec<u8> {
|
||||
let (one, two, five, max) = unsigned_const_set();
|
||||
if n == U256::ZERO {
|
||||
ModConst::modLhsZeroCall::new((d,)).abi_encode()
|
||||
} else if n == one {
|
||||
ModConst::modLhsOneCall::new((d,)).abi_encode()
|
||||
} else if n == two {
|
||||
ModConst::modLhsTwoCall::new((d,)).abi_encode()
|
||||
} else if n == five {
|
||||
ModConst::modLhsFiveCall::new((d,)).abi_encode()
|
||||
} else if n == max {
|
||||
ModConst::modLhsMaxCall::new((d,)).abi_encode()
|
||||
} else {
|
||||
panic!("no modLhsConst variant for n={n}")
|
||||
}
|
||||
}
|
||||
|
||||
fn sdiv_rhs_const_data(n: I256, d: I256) -> Vec<u8> {
|
||||
let (one, two, neg_two, five, neg_five, min, min_p1, max) = signed_const_set();
|
||||
if d == I256::ZERO {
|
||||
SdivConst::sdivRhsZeroCall::new((n,)).abi_encode()
|
||||
} else if d == one {
|
||||
SdivConst::sdivRhsOneCall::new((n,)).abi_encode()
|
||||
} else if d == I256::MINUS_ONE {
|
||||
SdivConst::sdivRhsNegOneCall::new((n,)).abi_encode()
|
||||
} else if d == two {
|
||||
SdivConst::sdivRhsTwoCall::new((n,)).abi_encode()
|
||||
} else if d == neg_two {
|
||||
SdivConst::sdivRhsNegTwoCall::new((n,)).abi_encode()
|
||||
} else if d == five {
|
||||
SdivConst::sdivRhsFiveCall::new((n,)).abi_encode()
|
||||
} else if d == neg_five {
|
||||
SdivConst::sdivRhsNegFiveCall::new((n,)).abi_encode()
|
||||
} else if d == min {
|
||||
SdivConst::sdivRhsMinCall::new((n,)).abi_encode()
|
||||
} else if d == min_p1 {
|
||||
SdivConst::sdivRhsMinPlusOneCall::new((n,)).abi_encode()
|
||||
} else if d == max {
|
||||
SdivConst::sdivRhsMaxCall::new((n,)).abi_encode()
|
||||
} else {
|
||||
panic!("no sdivRhsConst variant for d={d}")
|
||||
}
|
||||
}
|
||||
|
||||
fn sdiv_lhs_const_data(n: I256, d: I256) -> Vec<u8> {
|
||||
let (one, two, neg_two, five, neg_five, min, min_p1, max) = signed_const_set();
|
||||
if n == I256::ZERO {
|
||||
SdivConst::sdivLhsZeroCall::new((d,)).abi_encode()
|
||||
} else if n == one {
|
||||
SdivConst::sdivLhsOneCall::new((d,)).abi_encode()
|
||||
} else if n == I256::MINUS_ONE {
|
||||
SdivConst::sdivLhsNegOneCall::new((d,)).abi_encode()
|
||||
} else if n == two {
|
||||
SdivConst::sdivLhsTwoCall::new((d,)).abi_encode()
|
||||
} else if n == neg_two {
|
||||
SdivConst::sdivLhsNegTwoCall::new((d,)).abi_encode()
|
||||
} else if n == five {
|
||||
SdivConst::sdivLhsFiveCall::new((d,)).abi_encode()
|
||||
} else if n == neg_five {
|
||||
SdivConst::sdivLhsNegFiveCall::new((d,)).abi_encode()
|
||||
} else if n == min {
|
||||
SdivConst::sdivLhsMinCall::new((d,)).abi_encode()
|
||||
} else if n == min_p1 {
|
||||
SdivConst::sdivLhsMinPlusOneCall::new((d,)).abi_encode()
|
||||
} else if n == max {
|
||||
SdivConst::sdivLhsMaxCall::new((d,)).abi_encode()
|
||||
} else {
|
||||
panic!("no sdivLhsConst variant for n={n}")
|
||||
}
|
||||
}
|
||||
|
||||
fn smod_rhs_const_data(n: I256, d: I256) -> Vec<u8> {
|
||||
let (one, two, neg_two, five, neg_five, min, _min_p1, max) = signed_const_set();
|
||||
if d == I256::ZERO {
|
||||
SmodConst::smodRhsZeroCall::new((n,)).abi_encode()
|
||||
} else if d == one {
|
||||
SmodConst::smodRhsOneCall::new((n,)).abi_encode()
|
||||
} else if d == I256::MINUS_ONE {
|
||||
SmodConst::smodRhsNegOneCall::new((n,)).abi_encode()
|
||||
} else if d == two {
|
||||
SmodConst::smodRhsTwoCall::new((n,)).abi_encode()
|
||||
} else if d == neg_two {
|
||||
SmodConst::smodRhsNegTwoCall::new((n,)).abi_encode()
|
||||
} else if d == five {
|
||||
SmodConst::smodRhsFiveCall::new((n,)).abi_encode()
|
||||
} else if d == neg_five {
|
||||
SmodConst::smodRhsNegFiveCall::new((n,)).abi_encode()
|
||||
} else if d == min {
|
||||
SmodConst::smodRhsMinCall::new((n,)).abi_encode()
|
||||
} else if d == max {
|
||||
SmodConst::smodRhsMaxCall::new((n,)).abi_encode()
|
||||
} else {
|
||||
panic!("no smodRhsConst variant for d={d}")
|
||||
}
|
||||
}
|
||||
|
||||
fn smod_lhs_const_data(n: I256, d: I256) -> Vec<u8> {
|
||||
let (one, two, neg_two, five, neg_five, min, _min_p1, max) = signed_const_set();
|
||||
if n == I256::ZERO {
|
||||
SmodConst::smodLhsZeroCall::new((d,)).abi_encode()
|
||||
} else if n == one {
|
||||
SmodConst::smodLhsOneCall::new((d,)).abi_encode()
|
||||
} else if n == I256::MINUS_ONE {
|
||||
SmodConst::smodLhsNegOneCall::new((d,)).abi_encode()
|
||||
} else if n == two {
|
||||
SmodConst::smodLhsTwoCall::new((d,)).abi_encode()
|
||||
} else if n == neg_two {
|
||||
SmodConst::smodLhsNegTwoCall::new((d,)).abi_encode()
|
||||
} else if n == five {
|
||||
SmodConst::smodLhsFiveCall::new((d,)).abi_encode()
|
||||
} else if n == neg_five {
|
||||
SmodConst::smodLhsNegFiveCall::new((d,)).abi_encode()
|
||||
} else if n == min {
|
||||
SmodConst::smodLhsMinCall::new((d,)).abi_encode()
|
||||
} else if n == max {
|
||||
SmodConst::smodLhsMaxCall::new((d,)).abi_encode()
|
||||
} else {
|
||||
panic!("no smodLhsConst variant for n={n}")
|
||||
}
|
||||
}
|
||||
|
||||
fn push_call(actions: &mut Vec<SpecsAction>, dest: TestAddress, data: Vec<u8>) {
|
||||
actions.push(Call {
|
||||
origin: TestAddress::Alice,
|
||||
dest,
|
||||
value: 0,
|
||||
gas_limit: None,
|
||||
storage_deposit_limit: None,
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsigned_division_half_const() {
|
||||
let mut actions = instantiate("contracts/DivisionArithmeticsConst.sol", "DivConst");
|
||||
let (one, two, five, _max) = unsigned_const_set();
|
||||
let pairs = [
|
||||
(five, five),
|
||||
(five, one),
|
||||
(U256::ZERO, U256::MAX),
|
||||
(five, two),
|
||||
(one, U256::ZERO),
|
||||
];
|
||||
for (n, d) in pairs {
|
||||
push_call(
|
||||
&mut actions,
|
||||
TestAddress::Instantiated(0),
|
||||
div_rhs_const_data(n, d),
|
||||
);
|
||||
push_call(
|
||||
&mut actions,
|
||||
TestAddress::Instantiated(0),
|
||||
div_lhs_const_data(n, d),
|
||||
);
|
||||
}
|
||||
run_differential(actions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsigned_division_both_const() {
|
||||
let mut actions = instantiate_yul("contracts/DivBothConst.yul", "DivBothConst");
|
||||
let pair_count = 5;
|
||||
for i in 0..pair_count {
|
||||
push_call(
|
||||
&mut actions,
|
||||
TestAddress::Instantiated(0),
|
||||
yul_which_calldata(i),
|
||||
);
|
||||
}
|
||||
run_differential(actions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signed_division_half_const() {
|
||||
let mut actions = instantiate("contracts/DivisionArithmeticsConst.sol", "SdivConst");
|
||||
let (one, two, neg_two, five, neg_five, _min, _min_p1, _max) = signed_const_set();
|
||||
let pairs = [
|
||||
(five, five),
|
||||
(five, one),
|
||||
(I256::ZERO, I256::MAX),
|
||||
(I256::ZERO, I256::MINUS_ONE),
|
||||
(five, two),
|
||||
(five, I256::MINUS_ONE),
|
||||
(I256::MINUS_ONE, neg_two),
|
||||
(neg_five, neg_five),
|
||||
(neg_five, two),
|
||||
(I256::MINUS_ONE, I256::MIN),
|
||||
(one, I256::ZERO),
|
||||
(I256::MIN, I256::MINUS_ONE),
|
||||
(I256::MIN + I256::ONE, I256::MINUS_ONE),
|
||||
];
|
||||
for (n, d) in pairs {
|
||||
push_call(
|
||||
&mut actions,
|
||||
TestAddress::Instantiated(0),
|
||||
sdiv_rhs_const_data(n, d),
|
||||
);
|
||||
push_call(
|
||||
&mut actions,
|
||||
TestAddress::Instantiated(0),
|
||||
sdiv_lhs_const_data(n, d),
|
||||
);
|
||||
}
|
||||
run_differential(actions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signed_division_both_const() {
|
||||
let mut actions = instantiate_yul("contracts/SdivBothConst.yul", "SdivBothConst");
|
||||
let pair_count = 13;
|
||||
for i in 0..pair_count {
|
||||
push_call(
|
||||
&mut actions,
|
||||
TestAddress::Instantiated(0),
|
||||
yul_which_calldata(i),
|
||||
);
|
||||
}
|
||||
run_differential(actions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsigned_remainder_half_const() {
|
||||
let mut actions = instantiate("contracts/DivisionArithmeticsConst.sol", "ModConst");
|
||||
let (one, two, five, _max) = unsigned_const_set();
|
||||
let pairs = [
|
||||
(five, five),
|
||||
(five, one),
|
||||
(U256::ZERO, U256::MAX),
|
||||
(U256::MAX, U256::MAX),
|
||||
(five, two),
|
||||
(two, five),
|
||||
(U256::MAX, U256::ZERO),
|
||||
];
|
||||
for (n, d) in pairs {
|
||||
push_call(
|
||||
&mut actions,
|
||||
TestAddress::Instantiated(0),
|
||||
mod_rhs_const_data(n, d),
|
||||
);
|
||||
push_call(
|
||||
&mut actions,
|
||||
TestAddress::Instantiated(0),
|
||||
mod_lhs_const_data(n, d),
|
||||
);
|
||||
}
|
||||
run_differential(actions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsigned_remainder_both_const() {
|
||||
let mut actions = instantiate_yul("contracts/ModBothConst.yul", "ModBothConst");
|
||||
let pair_count = 7;
|
||||
for i in 0..pair_count {
|
||||
push_call(
|
||||
&mut actions,
|
||||
TestAddress::Instantiated(0),
|
||||
yul_which_calldata(i),
|
||||
);
|
||||
}
|
||||
run_differential(actions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signed_remainder_half_const() {
|
||||
let mut actions = instantiate("contracts/DivisionArithmeticsConst.sol", "SmodConst");
|
||||
let (one, two, neg_two, five, neg_five, _min, _min_p1, _max) = signed_const_set();
|
||||
let pairs = [
|
||||
(five, five),
|
||||
(five, one),
|
||||
(I256::ZERO, I256::MAX),
|
||||
(I256::MAX, I256::MAX),
|
||||
(five, two),
|
||||
(two, five),
|
||||
(five, neg_five),
|
||||
(five, I256::MINUS_ONE),
|
||||
(five, neg_two),
|
||||
(neg_five, two),
|
||||
(neg_two, five),
|
||||
(neg_five, neg_five),
|
||||
(neg_five, I256::MINUS_ONE),
|
||||
(neg_five, neg_two),
|
||||
(neg_two, neg_five),
|
||||
(I256::MIN, I256::MINUS_ONE),
|
||||
(I256::ZERO, I256::ZERO),
|
||||
];
|
||||
for (n, d) in pairs {
|
||||
push_call(
|
||||
&mut actions,
|
||||
TestAddress::Instantiated(0),
|
||||
smod_rhs_const_data(n, d),
|
||||
);
|
||||
push_call(
|
||||
&mut actions,
|
||||
TestAddress::Instantiated(0),
|
||||
smod_lhs_const_data(n, d),
|
||||
);
|
||||
}
|
||||
run_differential(actions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signed_remainder_both_const() {
|
||||
let mut actions = instantiate_yul("contracts/SmodBothConst.yul", "SmodBothConst");
|
||||
let pair_count = 17;
|
||||
for i in 0..pair_count {
|
||||
push_call(
|
||||
&mut actions,
|
||||
TestAddress::Instantiated(0),
|
||||
yul_which_calldata(i),
|
||||
);
|
||||
}
|
||||
run_differential(actions);
|
||||
}
|
||||
|
||||
/// Surfaces the `smod(INT_MIN, -1)` LLVM-UB const-fold bug from
|
||||
/// paritytech/revive#524. See `SmodIntMinNegOneBug.yul` for why this specific
|
||||
/// fixture is shaped the way it is. Expected to FAIL until the bug is fixed.
|
||||
#[test]
|
||||
fn signed_remainder_int_min_neg_one_bug() {
|
||||
let mut actions = instantiate_yul("contracts/SmodIntMinNegOneBug.yul", "SmodIntMinNegOneBug");
|
||||
let mut tag = vec![0u8; 32];
|
||||
tag[28..32].copy_from_slice(&[0xde, 0xad, 0xbe, 0xef]);
|
||||
push_call(&mut actions, TestAddress::Instantiated(0), tag);
|
||||
run_differential(actions);
|
||||
}
|
||||
|
||||
/// Sibling to `signed_remainder_int_min_neg_one_bug`. Per the issue, sdiv is
|
||||
/// claimed to be guarded; this test pins that guard so regressions surface.
|
||||
#[test]
|
||||
fn signed_division_int_min_neg_one_bug() {
|
||||
let mut actions = instantiate_yul("contracts/SdivIntMinNegOneBug.yul", "SdivIntMinNegOneBug");
|
||||
let mut tag = vec![0u8; 32];
|
||||
tag[28..32].copy_from_slice(&[0xde, 0xad, 0xbe, 0xef]);
|
||||
push_call(&mut actions, TestAddress::Instantiated(0), tag);
|
||||
run_differential(actions);
|
||||
}
|
||||
|
||||
/// Build the standard "tag plus extra calldata words" payload used by the
|
||||
/// single-case probe fixtures.
|
||||
fn probe_calldata(extra_words: &[U256]) -> Vec<u8> {
|
||||
let mut data = vec![0u8; 32 * (1 + extra_words.len())];
|
||||
data[28..32].copy_from_slice(&[0xde, 0xad, 0xbe, 0xef]);
|
||||
for (i, word) in extra_words.iter().enumerate() {
|
||||
let off = 32 * (i + 1);
|
||||
data[off..off + 32].copy_from_slice(&word.to_be_bytes::<32>());
|
||||
}
|
||||
data
|
||||
}
|
||||
|
||||
fn run_probe(path: &str, contract: &str, extra: &[U256]) {
|
||||
let mut actions = instantiate_yul(path, contract);
|
||||
push_call(
|
||||
&mut actions,
|
||||
TestAddress::Instantiated(0),
|
||||
probe_calldata(extra),
|
||||
);
|
||||
run_differential(actions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn probe_shl_overflow() {
|
||||
run_probe(
|
||||
"contracts/ShlOverflowProbe.yul",
|
||||
"ShlOverflowProbe",
|
||||
&[U256::from(0x123456789abcdef_u64)],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn probe_shr_overflow() {
|
||||
run_probe(
|
||||
"contracts/ShrOverflowProbe.yul",
|
||||
"ShrOverflowProbe",
|
||||
&[U256::from(0x123456789abcdef_u64)],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn probe_sar_overflow() {
|
||||
run_probe("contracts/SarOverflowProbe.yul", "SarOverflowProbe", &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn probe_addmod_zero() {
|
||||
run_probe(
|
||||
"contracts/AddModZeroProbe.yul",
|
||||
"AddModZeroProbe",
|
||||
&[U256::from(42), U256::from(99)],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn probe_mulmod_zero() {
|
||||
run_probe(
|
||||
"contracts/MulModZeroProbe.yul",
|
||||
"MulModZeroProbe",
|
||||
&[U256::from(42), U256::from(99)],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn probe_signextend_oob() {
|
||||
run_probe(
|
||||
"contracts/SignExtendOobProbe.yul",
|
||||
"SignExtendOobProbe",
|
||||
&[U256::MAX],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn probe_byte_oob() {
|
||||
run_probe("contracts/ByteOobProbe.yul", "ByteOobProbe", &[U256::MAX]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn probe_exp_zero_zero() {
|
||||
run_probe("contracts/ExpZeroZeroProbe.yul", "ExpZeroZeroProbe", &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ext_code_hash() {
|
||||
let mut actions = instantiate("contracts/ExtCode.sol", "ExtCode");
|
||||
|
||||
Reference in new issue
Block a user