EIP-1702: Generalized Account Versioning Scheme (#10771)

* EIP-1702: Generalized Account Versioning Scheme

* Fix pWASM's `create` and `create2` contract creation version

* Update ethcore/src/snapshot/account.rs

Co-Authored-By: Andronik Ordian <write@reusable.software>

* Update ethcore/src/factory.rs

Co-Authored-By: Andronik Ordian <write@reusable.software>

* Add pWasm frontend config

* Add snapshot testing with version

* Fix merge conflict
This commit is contained in:
Wei Tang 2019-07-08 12:03:27 +02:00 committed by David
parent fe7bc545bf
commit 141f6a047e
26 changed files with 305 additions and 94 deletions

View File

@ -118,6 +118,8 @@ struct InterpreterParams {
pub code_address: Address,
/// Hash of currently executed code.
pub code_hash: Option<H256>,
/// Code version.
pub code_version: U256,
/// Receive address. Usually equal to code_address,
/// except when called using CALLCODE.
pub address: Address,
@ -144,6 +146,7 @@ impl From<ActionParams> for InterpreterParams {
InterpreterParams {
code_address: params.code_address,
code_hash: params.code_hash,
code_version: params.code_version,
address: params.address,
sender: params.sender,
origin: params.origin,
@ -531,7 +534,7 @@ impl<Cost: CostType> Interpreter<Cost> {
let contract_code = self.mem.read_slice(init_off, init_size);
let create_result = ext.create(&create_gas.as_u256(), &endowment, contract_code, address_scheme, true);
let create_result = ext.create(&create_gas.as_u256(), &endowment, contract_code, &self.params.code_version, address_scheme, true);
return match create_result {
Ok(ContractCreateResult::Created(address, gas_left)) => {
self.stack.push(address_to_u256(address));

View File

@ -33,7 +33,7 @@ use hash_db::HashDB;
use kvdb::DBValue;
use parking_lot::Mutex;
use request::{self as net_request, IncompleteRequest, CompleteRequest, Output, OutputKind, Field};
use rlp::{RlpStream, Rlp};
use rlp::RlpStream;
use trie::Trie;
use vm::EnvInfo;
@ -984,13 +984,7 @@ impl Account {
match TrieDB::new(&db, &state_root).and_then(|t| t.get(keccak(&self.address).as_bytes()))? {
Some(val) => {
let rlp = Rlp::new(&val);
Ok(Some(BasicAccount {
nonce: rlp.val_at(0)?,
balance: rlp.val_at(1)?,
storage_root: rlp.val_at(2)?,
code_hash: rlp.val_at(3)?,
}))
Ok(Some(rlp::decode::<BasicAccount>(&val)?))
},
None => {
trace!(target: "on_demand", "Account {:?} not found", self.address);

View File

@ -46,6 +46,9 @@ pub struct PodAccount {
pub code: Option<Bytes>,
/// The storage of the account.
pub storage: BTreeMap<H256, H256>,
/// The version of the account.
#[serde(default)]
pub version: U256,
}
fn opt_bytes_to_hex<S>(opt_bytes: &Option<Bytes>, serializer: S) -> Result<S::Ok, S::Error>
@ -93,6 +96,7 @@ impl From<ethjson::blockchain::Account> for PodAccount {
let value: U256 = value.into();
(BigEndianHash::from_uint(&key), BigEndianHash::from_uint(&value))
}).collect(),
version: a.version.into(),
}
}
}
@ -108,6 +112,7 @@ impl From<ethjson::spec::Account> for PodAccount {
let value: U256 = value.into();
(BigEndianHash::from_uint(&key), BigEndianHash::from_uint(&value))
}).collect()),
version: a.version.map_or_else(U256::zero, Into::into),
}
}
}
@ -165,7 +170,9 @@ mod test {
#[test]
fn existence() {
let a = PodAccount{balance: 69.into(), nonce: 0.into(), code: Some(vec![]), storage: map![]};
let a = PodAccount {
balance: 69.into(), nonce: 0.into(), code: Some(vec![]), storage: map![], version: 0.into(),
};
assert_eq!(diff_pod(Some(&a), Some(&a)), None);
assert_eq!(diff_pod(None, Some(&a)), Some(AccountDiff{
balance: Diff::Born(69.into()),
@ -177,8 +184,12 @@ mod test {
#[test]
fn basic() {
let a = PodAccount{balance: 69.into(), nonce: 0.into(), code: Some(vec![]), storage: map![]};
let b = PodAccount{balance: 42.into(), nonce: 1.into(), code: Some(vec![]), storage: map![]};
let a = PodAccount {
balance: 69.into(), nonce: 0.into(), code: Some(vec![]), storage: map![], version: 0.into(),
};
let b = PodAccount {
balance: 42.into(), nonce: 1.into(), code: Some(vec![]), storage: map![], version: 0.into(),
};
assert_eq!(diff_pod(Some(&a), Some(&b)), Some(AccountDiff {
balance: Diff::Changed(69.into(), 42.into()),
nonce: Diff::Changed(0.into(), 1.into()),
@ -189,8 +200,12 @@ mod test {
#[test]
fn code() {
let a = PodAccount{balance: 0.into(), nonce: 0.into(), code: Some(vec![]), storage: map![]};
let b = PodAccount{balance: 0.into(), nonce: 1.into(), code: Some(vec![0]), storage: map![]};
let a = PodAccount {
balance: 0.into(), nonce: 0.into(), code: Some(vec![]), storage: map![], version: 0.into(),
};
let b = PodAccount {
balance: 0.into(), nonce: 1.into(), code: Some(vec![0]), storage: map![], version: 0.into(),
};
assert_eq!(diff_pod(Some(&a), Some(&b)), Some(AccountDiff {
balance: Diff::Same,
nonce: Diff::Changed(0.into(), 1.into()),
@ -214,6 +229,7 @@ mod test {
H256::from_low_u64_be(6) => H256::from_low_u64_be(0),
H256::from_low_u64_be(7) => H256::from_low_u64_be(0)
],
version: 0.into(),
};
let b = PodAccount {
balance: 0.into(),
@ -227,7 +243,8 @@ mod test {
H256::from_low_u64_be(7) => H256::from_low_u64_be(7),
H256::from_low_u64_be(8) => H256::from_low_u64_be(0),
H256::from_low_u64_be(9) => H256::from_low_u64_be(9)
]
],
version: 0.into(),
};
assert_eq!(diff_pod(Some(&a), Some(&b)), Some(AccountDiff {
balance: Diff::Same,

View File

@ -313,9 +313,9 @@ impl<'a> CallCreateExecutive<'a> {
let prev_bal = state.balance(&params.address)?;
if let ActionValue::Transfer(val) = params.value {
state.sub_balance(&params.sender, &val, &mut substate.to_cleanup_mode(&schedule))?;
state.new_contract(&params.address, val.saturating_add(prev_bal), nonce_offset)?;
state.new_contract(&params.address, val.saturating_add(prev_bal), nonce_offset, params.code_version)?;
} else {
state.new_contract(&params.address, prev_bal, nonce_offset)?;
state.new_contract(&params.address, prev_bal, nonce_offset, params.code_version)?;
}
Ok(())
@ -451,12 +451,15 @@ impl<'a> CallCreateExecutive<'a> {
let origin_info = OriginInfo::from(&params);
let exec = self.factory.create(params, self.schedule, self.depth);
let out = {
let out = match exec {
Some(exec) => {
let mut ext = Self::as_externalities(state, self.info, self.machine, self.schedule, self.depth, self.stack_depth, self.static_flag, &origin_info, &mut unconfirmed_substate, OutputPolicy::Return, tracer, vm_tracer);
match exec.exec(&mut ext) {
Ok(val) => Ok(val.finalize(ext)),
Err(err) => Err(err),
}
},
None => Ok(Err(vm::Error::OutOfGas)),
};
let res = match out {
@ -499,12 +502,15 @@ impl<'a> CallCreateExecutive<'a> {
let origin_info = OriginInfo::from(&params);
let exec = self.factory.create(params, self.schedule, self.depth);
let out = {
let out = match exec {
Some(exec) => {
let mut ext = Self::as_externalities(state, self.info, self.machine, self.schedule, self.depth, self.stack_depth, self.static_flag, &origin_info, &mut unconfirmed_substate, OutputPolicy::InitContract, tracer, vm_tracer);
match exec.exec(&mut ext) {
Ok(val) => Ok(val.finalize(ext)),
Err(err) => Err(err),
}
},
None => Ok(Err(vm::Error::OutOfGas)),
};
let res = match out {
@ -874,6 +880,7 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> {
gas_price: t.gas_price,
value: ActionValue::Transfer(t.value),
code: Some(Arc::new(t.data.clone())),
code_version: schedule.latest_version,
data: None,
call_type: CallType::None,
params_type: vm::ParamsType::Embedded,
@ -896,6 +903,7 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> {
value: ActionValue::Transfer(t.value),
code: self.state.code(address)?,
code_hash: self.state.code_hash(address)?,
code_version: self.state.code_version(address)?,
data: Some(t.data.clone()),
call_type: CallType::Call,
params_type: vm::ParamsType::Separate,
@ -2093,13 +2101,13 @@ mod tests {
let k = H256::zero();
let mut state = get_temp_state_with_factory(factory.clone());
state.new_contract(&x1, U256::zero(), U256::from(1)).unwrap();
state.new_contract(&x1, U256::zero(), U256::from(1), U256::zero()).unwrap();
state.init_code(&x1, "600160005560006000556001600055".from_hex().unwrap()).unwrap();
state.new_contract(&x2, U256::zero(), U256::from(1)).unwrap();
state.new_contract(&x2, U256::zero(), U256::from(1), U256::zero()).unwrap();
state.init_code(&x2, "600060005560016000556000600055".from_hex().unwrap()).unwrap();
state.new_contract(&y1, U256::zero(), U256::from(1)).unwrap();
state.new_contract(&y1, U256::zero(), U256::from(1), U256::zero()).unwrap();
state.init_code(&y1, "600060006000600061100062fffffff4".from_hex().unwrap()).unwrap();
state.new_contract(&y2, U256::zero(), U256::from(1)).unwrap();
state.new_contract(&y2, U256::zero(), U256::from(1), U256::zero()).unwrap();
state.init_code(&y2, "600060006000600061100162fffffff4".from_hex().unwrap()).unwrap();
let info = EnvInfo::default();

View File

@ -160,10 +160,11 @@ impl<'a, T: 'a, V: 'a, B: 'a> Ext for Externalities<'a, T, V, B>
if self.env_info.number + 256 >= self.machine.params().eip210_transition {
let blockhash_contract_address = self.machine.params().eip210_contract_address;
let code_res = self.state.code(&blockhash_contract_address)
.and_then(|code| self.state.code_hash(&blockhash_contract_address).map(|hash| (code, hash)));
.and_then(|code| self.state.code_hash(&blockhash_contract_address).map(|hash| (code, hash)))
.and_then(|(code, hash)| self.state.code_version(&blockhash_contract_address).map(|version| (code, hash, version)));
let (code, code_hash) = match code_res {
Ok((code, hash)) => (code, hash),
let (code, code_hash, code_version) = match code_res {
Ok((code, hash, version)) => (code, hash, version),
Err(_) => return H256::zero(),
};
let data: H256 = BigEndianHash::from_uint(number);
@ -178,6 +179,7 @@ impl<'a, T: 'a, V: 'a, B: 'a> Ext for Externalities<'a, T, V, B>
gas_price: 0.into(),
code: code,
code_hash: code_hash,
code_version: code_version,
data: Some(data.as_bytes().to_vec()),
call_type: CallType::Call,
params_type: vm::ParamsType::Separate,
@ -214,6 +216,7 @@ impl<'a, T: 'a, V: 'a, B: 'a> Ext for Externalities<'a, T, V, B>
gas: &U256,
value: &U256,
code: &[u8],
parent_version: &U256,
address_scheme: CreateContractAddress,
trap: bool,
) -> ::std::result::Result<ContractCreateResult, TrapKind> {
@ -237,6 +240,7 @@ impl<'a, T: 'a, V: 'a, B: 'a> Ext for Externalities<'a, T, V, B>
value: ActionValue::Transfer(*value),
code: Some(Arc::new(code.to_vec())),
code_hash: code_hash,
code_version: *parent_version,
data: None,
call_type: CallType::None,
params_type: vm::ParamsType::Embedded,
@ -275,10 +279,11 @@ impl<'a, T: 'a, V: 'a, B: 'a> Ext for Externalities<'a, T, V, B>
trace!(target: "externalities", "call");
let code_res = self.state.code(code_address)
.and_then(|code| self.state.code_hash(code_address).map(|hash| (code, hash)));
.and_then(|code| self.state.code_hash(code_address).map(|hash| (code, hash)))
.and_then(|(code, hash)| self.state.code_version(code_address).map(|version| (code, hash, version)));
let (code, code_hash) = match code_res {
Ok((code, hash)) => (code, hash),
let (code, code_hash, code_version) = match code_res {
Ok((code, hash, version)) => (code, hash, version),
Err(_) => return Ok(MessageCallResult::Failed),
};
@ -292,6 +297,7 @@ impl<'a, T: 'a, V: 'a, B: 'a> Ext for Externalities<'a, T, V, B>
gas_price: self.origin_info.gas_price,
code: code,
code_hash: code_hash,
code_version: code_version,
data: Some(data.to_vec()),
call_type: call_type,
params_type: vm::ParamsType::Separate,
@ -611,7 +617,7 @@ mod tests {
let address = {
let mut ext = Externalities::new(state, &setup.env_info, &setup.machine, &setup.schedule, 0, 0, &origin_info, &mut setup.sub_state, OutputPolicy::InitContract, &mut tracer, &mut vm_tracer, false);
match ext.create(&U256::max_value(), &U256::zero(), &[], CreateContractAddress::FromSenderAndNonce, false) {
match ext.create(&U256::max_value(), &U256::zero(), &[], &U256::zero(), CreateContractAddress::FromSenderAndNonce, false) {
Ok(ContractCreateResult::Created(address, _)) => address,
_ => panic!("Test create failed; expected Created, got Failed/Reverted."),
}
@ -633,7 +639,7 @@ mod tests {
let address = {
let mut ext = Externalities::new(state, &setup.env_info, &setup.machine, &setup.schedule, 0, 0, &origin_info, &mut setup.sub_state, OutputPolicy::InitContract, &mut tracer, &mut vm_tracer, false);
match ext.create(&U256::max_value(), &U256::zero(), &[], CreateContractAddress::FromSenderSaltAndCodeHash(H256::zero()), false) {
match ext.create(&U256::max_value(), &U256::zero(), &[], &U256::zero(), CreateContractAddress::FromSenderSaltAndCodeHash(H256::zero()), false) {
Ok(ContractCreateResult::Created(address, _)) => address,
_ => panic!("Test create failed; expected Created, got Failed/Reverted."),
}

View File

@ -17,8 +17,9 @@
use trie::TrieFactory;
use ethtrie::RlpCodec;
use account_db::Factory as AccountFactory;
use ethereum_types::U256;
use evm::{Factory as EvmFactory, VMType};
use vm::{Exec, ActionParams, Schedule};
use vm::{Exec, ActionParams, VersionedSchedule, Schedule};
use wasm::WasmInterpreter;
use keccak_hasher::KeccakHasher;
@ -31,11 +32,22 @@ pub struct VmFactory {
}
impl VmFactory {
pub fn create(&self, params: ActionParams, schedule: &Schedule, depth: usize) -> Box<dyn Exec> {
if schedule.wasm.is_some() && params.code.as_ref().map_or(false, |code| code.len() > 4 && &code[0..4] == WASM_MAGIC_NUMBER) {
pub fn create(&self, params: ActionParams, schedule: &Schedule, depth: usize) -> Option<Box<dyn Exec>> {
if params.code_version.is_zero() {
Some(if schedule.wasm.is_some() && schedule.versions.is_empty() && params.code.as_ref().map_or(false, |code| code.len() > 4 && &code[0..4] == WASM_MAGIC_NUMBER) {
Box::new(WasmInterpreter::new(params))
} else {
self.evm.create(params, schedule, depth)
})
} else {
let version_config = schedule.versions.get(&params.code_version);
match version_config {
Some(VersionedSchedule::PWasm) => {
Some(Box::new(WasmInterpreter::new(params)))
},
None => None,
}
}
}

View File

@ -146,6 +146,7 @@ impl<'a, T: 'a, V: 'a, B: 'a> Ext for TestExt<'a, T, V, B>
gas: &U256,
value: &U256,
code: &[u8],
_code_version: &U256,
address: CreateContractAddress,
_trap: bool
) -> Result<ContractCreateResult, vm::TrapKind> {
@ -298,7 +299,7 @@ fn do_json_test_for<H: FnMut(&str, HookType)>(vm_type: &VMType, json_data: &[u8]
&mut tracer,
&mut vm_tracer,
));
let mut evm = vm_factory.create(params, &schedule, 0);
let mut evm = vm_factory.create(params, &schedule, 0).expect("Current tests are all of version 0; factory always return Some; qed");
let res = evm.exec(&mut ex).ok().expect("TestExt never trap; resume error never happens; qed");
// a return in finalize will not alter callcreates
let callcreates = ex.callcreates.clone();

View File

@ -176,6 +176,7 @@ impl Machine {
value: value.unwrap_or_else(|| ActionValue::Transfer(0.into())),
code,
code_hash,
code_version: 0.into(),
data,
call_type: call_type.unwrap_or(CallType::Call),
params_type: ParamsType::Separate,

View File

@ -89,6 +89,7 @@ mod test {
nonce: 0.into(),
code: Some(Vec::new()),
storage: map![],
version: 0.into(),
}
]);
assert_eq!(super::diff_pod(&a, &PodState::default()), StateDiff { raw: map![
@ -117,6 +118,7 @@ mod test {
nonce: 0.into(),
code: Some(Vec::new()),
storage: map![],
version: 0.into(),
}
]);
let b = PodState::from(map![
@ -125,12 +127,14 @@ mod test {
nonce: 0.into(),
code: Some(Vec::new()),
storage: map![],
version: 0.into(),
},
Address::from_low_u64_be(2) => PodAccount {
balance: 69.into(),
nonce: 0.into(),
code: Some(Vec::new()),
storage: map![],
version: 0.into(),
}
]);
assert_eq!(super::diff_pod(&a, &b), StateDiff { raw: map![
@ -159,12 +163,14 @@ mod test {
nonce: 0.into(),
code: Some(Vec::new()),
storage: map![],
version: 0.into(),
},
Address::from_low_u64_be(2) => PodAccount {
balance: 69.into(),
nonce: 0.into(),
code: Some(Vec::new()),
storage: map![],
version: 0.into(),
}
]);
let b = PodState::from(map![
@ -173,12 +179,14 @@ mod test {
nonce: 1.into(),
code: Some(Vec::new()),
storage: map![],
version: 0.into(),
},
Address::from_low_u64_be(2) => PodAccount {
balance: 69.into(),
nonce: 0.into(),
code: Some(Vec::new()),
storage: map![],
version: 0.into(),
}
]);
assert_eq!(super::diff_pod(&a, &b), StateDiff { raw: map![

View File

@ -35,6 +35,7 @@ const ACC_EMPTY: BasicAccount = BasicAccount {
balance: U256([0, 0, 0, 0]),
storage_root: KECCAK_NULL_RLP,
code_hash: KECCAK_EMPTY,
code_version: U256([0, 0, 0, 0]),
};
// whether an encoded account has code and how it is referred to.
@ -84,7 +85,11 @@ pub fn to_fat_rlps(
let mut leftover: Option<Vec<u8>> = None;
loop {
account_stream.append(account_hash);
account_stream.begin_list(5);
let use_short_version = acc.code_version.is_zero();
match use_short_version {
true => { account_stream.begin_list(5); },
false => { account_stream.begin_list(6); },
}
account_stream.append(&acc.nonce)
.append(&acc.balance);
@ -107,6 +112,10 @@ pub fn to_fat_rlps(
}
}
if !use_short_version {
account_stream.append(&acc.code_version);
}
account_stream.begin_unbounded_list();
if account_stream.len() > target_chunk_size {
// account does not fit, push an empty record to mark a new chunk
@ -170,6 +179,12 @@ pub fn from_fat_rlp(
return Ok((ACC_EMPTY, None));
}
let use_short_version = match rlp.item_count()? {
5 => true,
6 => false,
_ => return Err(rlp::DecoderError::RlpIncorrectListLen.into()),
};
let nonce = rlp.val_at(0)?;
let balance = rlp.val_at(1)?;
let code_state: CodeState = {
@ -193,13 +208,19 @@ pub fn from_fat_rlp(
}
};
let code_version = if use_short_version {
U256::zero()
} else {
rlp.val_at(4)?
};
{
let mut storage_trie = if storage_root.is_zero() {
TrieDBMut::new(acct_db, &mut storage_root)
} else {
TrieDBMut::from_existing(acct_db, &mut storage_root)?
};
let pairs = rlp.at(4)?;
let pairs = rlp.at(if use_short_version { 4 } else { 5 })?;
for pair_rlp in pairs.iter() {
let k: Bytes = pair_rlp.val_at(0)?;
let v: Bytes = pair_rlp.val_at(1)?;
@ -209,10 +230,11 @@ pub fn from_fat_rlp(
}
let acc = BasicAccount {
nonce: nonce,
balance: balance,
storage_root: storage_root,
code_hash: code_hash,
nonce,
balance,
storage_root,
code_hash,
code_version,
};
Ok((acc, new_code))
@ -246,6 +268,28 @@ mod tests {
balance: 123456789.into(),
storage_root: KECCAK_NULL_RLP,
code_hash: KECCAK_EMPTY,
code_version: 0.into(),
};
let thin_rlp = ::rlp::encode(&account);
assert_eq!(::rlp::decode::<BasicAccount>(&thin_rlp).unwrap(), account);
let p = Progress::default();
let fat_rlps = to_fat_rlps(&keccak(&addr), &account, &AccountDB::from_hash(db.as_hash_db(), keccak(addr)), &mut Default::default(), usize::max_value(), usize::max_value(), &p).unwrap();
let fat_rlp = Rlp::new(&fat_rlps[0]).at(1).unwrap();
assert_eq!(from_fat_rlp(&mut AccountDBMut::from_hash(db.as_hash_db_mut(), keccak(addr)), fat_rlp, H256::zero()).unwrap().0, account);
}
#[test]
fn encoding_version() {
let mut db = get_temp_state_db();
let addr = Address::random();
let account = BasicAccount {
nonce: 50.into(),
balance: 123456789.into(),
storage_root: KECCAK_NULL_RLP,
code_hash: KECCAK_EMPTY,
code_version: 1.into(),
};
let thin_rlp = ::rlp::encode(&account);
@ -270,6 +314,7 @@ mod tests {
balance: 987654321.into(),
storage_root: root,
code_hash: KECCAK_EMPTY,
code_version: 0.into(),
}
};
@ -297,6 +342,7 @@ mod tests {
balance: 987654321.into(),
storage_root: root,
code_hash: KECCAK_EMPTY,
code_version: 0.into(),
}
};
@ -337,6 +383,7 @@ mod tests {
balance: 123456789.into(),
storage_root: KECCAK_NULL_RLP,
code_hash,
code_version: 0.into(),
};
let account2 = BasicAccount {
@ -344,6 +391,7 @@ mod tests {
balance: 98765432123456789usize.into(),
storage_root: KECCAK_NULL_RLP,
code_hash,
code_version: 0.into(),
};
let mut used_code = HashSet::new();

View File

@ -31,7 +31,7 @@ use rustc_hex::{FromHex, ToHex};
use types::BlockNumber;
use types::encoded;
use types::header::Header;
use vm::{EnvInfo, CallType, ActionValue, ActionParams, ParamsType};
use vm::{EnvInfo, CallType, ActionValue, ActionParams, ParamsType, VersionedSchedule};
use builtin::Builtin;
use engines::{
@ -131,6 +131,9 @@ pub struct CommonParams {
pub remove_dust_contracts: bool,
/// Wasm activation blocknumber, if any disabled initially.
pub wasm_activation_transition: BlockNumber,
/// Wasm account version, activated after `wasm_activation_transition`. If this field is defined, do not use code
/// prefix to determine VM to execute.
pub wasm_version: Option<U256>,
/// Number of first block where KIP-4 rules begin. Only has effect if Wasm is activated.
pub kip4_transition: BlockNumber,
/// Number of first block where KIP-6 rules begin. Only has effect if Wasm is activated.
@ -208,6 +211,9 @@ impl CommonParams {
wasm.have_gasleft = true;
}
schedule.wasm = Some(wasm);
if let Some(version) = self.wasm_version {
schedule.versions.insert(version, VersionedSchedule::PWasm);
}
}
}
@ -327,6 +333,7 @@ impl From<ethjson::spec::Params> for CommonParams {
BlockNumber::max_value,
Into::into
),
wasm_version: p.wasm_version.map(Into::into),
kip4_transition: p.kip4_transition.map_or_else(
BlockNumber::max_value,
Into::into
@ -665,6 +672,7 @@ impl Spec {
let params = ActionParams {
code_address: address.clone(),
code_hash: Some(keccak(constructor)),
code_version: U256::zero(),
address: address.clone(),
sender: from.clone(),
origin: from.clone(),

View File

@ -497,14 +497,14 @@ impl<B: Backend> State<B> {
/// Create a new contract at address `contract`. If there is already an account at the address
/// it will have its code reset, ready for `init_code()`.
pub fn new_contract(&mut self, contract: &Address, balance: U256, nonce_offset: U256) -> TrieResult<()> {
pub fn new_contract(&mut self, contract: &Address, balance: U256, nonce_offset: U256, version: U256) -> TrieResult<()> {
let original_storage_root = self.original_storage_root(contract)?;
let (nonce, overflow) = self.account_start_nonce.overflowing_add(nonce_offset);
if overflow {
return Err(Box::new(TrieError::DecoderError(H256::from(*contract),
rlp::DecoderError::Custom("Nonce overflow".into()))));
}
self.insert_cache(contract, AccountEntry::new_dirty(Some(Account::new_contract(balance, nonce, original_storage_root))));
self.insert_cache(contract, AccountEntry::new_dirty(Some(Account::new_contract(balance, nonce, version, original_storage_root))));
Ok(())
}
@ -732,6 +732,12 @@ impl<B: Backend> State<B> {
|a| a.as_ref().map(|a| a.code_hash()))
}
/// Get an account's code version.
pub fn code_version(&self, a: &Address) -> TrieResult<U256> {
self.ensure_cached(a, RequireCache::None, true,
|a| a.as_ref().map(|a| *a.code_version()).unwrap_or(U256::zero()))
}
/// Get accounts' code size.
pub fn code_size(&self, a: &Address) -> TrieResult<Option<usize>> {
self.ensure_cached(a, RequireCache::CodeSize, true,
@ -790,13 +796,13 @@ impl<B: Backend> State<B> {
/// Initialise the code of account `a` so that it is `code`.
/// NOTE: Account should have been created with `new_contract`.
pub fn init_code(&mut self, a: &Address, code: Bytes) -> TrieResult<()> {
self.require_or_from(a, true, || Account::new_contract(0.into(), self.account_start_nonce, KECCAK_NULL_RLP), |_| {})?.init_code(code);
self.require_or_from(a, true, || Account::new_contract(0.into(), self.account_start_nonce, 0.into(), KECCAK_NULL_RLP), |_| {})?.init_code(code);
Ok(())
}
/// Reset the code of account `a` so that it is `code`.
pub fn reset_code(&mut self, a: &Address, code: Bytes) -> TrieResult<()> {
self.require_or_from(a, true, || Account::new_contract(0.into(), self.account_start_nonce, KECCAK_NULL_RLP), |_| {})?.reset_code(code);
self.require_or_from(a, true, || Account::new_contract(0.into(), self.account_start_nonce, 0.into(), KECCAK_NULL_RLP), |_| {})?.reset_code(code);
Ok(())
}
@ -1069,11 +1075,11 @@ impl<B: Backend> State<B> {
};
// Storage must be fetched after ensure_cached to avoid borrow problem.
(*acc.balance(), *acc.nonce(), all_keys, acc.code().map(|x| x.to_vec()))
(*acc.balance(), *acc.nonce(), all_keys, acc.code().map(|x| x.to_vec()), *acc.code_version())
})
})?;
if let Some((balance, nonce, storage_keys, code)) = account {
if let Some((balance, nonce, storage_keys, code, version)) = account {
let storage = storage_keys.into_iter().fold(Ok(BTreeMap::new()), |s: TrieResult<_>, key| {
let mut s = s?;
@ -1082,7 +1088,7 @@ impl<B: Backend> State<B> {
})?;
m.insert(address, PodAccount {
balance, nonce, storage, code
balance, nonce, storage, code, version
});
}
@ -1270,6 +1276,7 @@ impl<B: Backend> State<B> {
nonce: self.account_start_nonce,
code_hash: KECCAK_EMPTY,
storage_root: KECCAK_NULL_RLP,
code_version: 0.into(),
});
Ok((recorder.drain().into_iter().map(|r| r.data).collect(), account))
@ -2190,7 +2197,7 @@ mod tests {
let a = Address::zero();
let (root, db) = {
let mut state = get_temp_state();
state.require_or_from(&a, false, || Account::new_contract(42.into(), 0.into(), KECCAK_NULL_RLP), |_|{}).unwrap();
state.require_or_from(&a, false, || Account::new_contract(42.into(), 0.into(), 0.into(), KECCAK_NULL_RLP), |_|{}).unwrap();
state.init_code(&a, vec![1, 2, 3]).unwrap();
assert_eq!(state.code(&a).unwrap(), Some(Arc::new(vec![1u8, 2, 3])));
state.commit().unwrap();
@ -2426,7 +2433,7 @@ mod tests {
state.clear();
let c0 = state.checkpoint();
state.new_contract(&a, U256::zero(), U256::zero()).unwrap();
state.new_contract(&a, U256::zero(), U256::zero(), U256::zero()).unwrap();
let c1 = state.checkpoint();
state.set_storage(&a, k, BigEndianHash::from_uint(&U256::from(1))).unwrap();
let c2 = state.checkpoint();
@ -2486,7 +2493,7 @@ mod tests {
let cm1 = state.checkpoint();
let c0 = state.checkpoint();
state.new_contract(&a, U256::zero(), U256::zero()).unwrap();
state.new_contract(&a, U256::zero(), U256::zero(), U256::zero()).unwrap();
let c1 = state.checkpoint();
state.set_storage(&a, k, BigEndianHash::from_uint(&U256::from(1))).unwrap();
let c2 = state.checkpoint();
@ -2558,7 +2565,7 @@ mod tests {
let a = Address::from_low_u64_be(1000);
state.checkpoint(); // c1
state.new_contract(&a, U256::zero(), U256::zero()).unwrap();
state.new_contract(&a, U256::zero(), U256::zero(), U256::zero()).unwrap();
state.add_balance(&a, &U256::from(1), CleanupMode::ForceCreate).unwrap();
state.checkpoint(); // c2
state.add_balance(&a, &U256::from(1), CleanupMode::ForceCreate).unwrap();
@ -2585,7 +2592,7 @@ mod tests {
state.clear();
state.checkpoint(); // c1
state.new_contract(&a, U256::zero(), U256::zero()).unwrap();
state.new_contract(&a, U256::zero(), U256::zero(), U256::zero()).unwrap();
state.checkpoint(); // c2
state.set_storage(&a, k, BigEndianHash::from_uint(&U256::from(2))).unwrap();
state.revert_to_checkpoint(); // revert to c2
@ -2633,7 +2640,7 @@ mod tests {
state.add_balance(&b, &100.into(), CleanupMode::ForceCreate).unwrap(); // create a dust account
state.add_balance(&c, &101.into(), CleanupMode::ForceCreate).unwrap(); // create a normal account
state.add_balance(&d, &99.into(), CleanupMode::ForceCreate).unwrap(); // create another dust account
state.new_contract(&e, 100.into(), 1.into()).unwrap(); // create a contract account
state.new_contract(&e, 100.into(), 1.into(), 0.into()).unwrap(); // create a contract account
state.init_code(&e, vec![0x00]).unwrap();
state.commit().unwrap();
state.drop()
@ -2685,7 +2692,8 @@ mod tests {
balance: U256::from(100),
nonce: U256::zero(),
code: Some(Default::default()),
storage: Default::default()
storage: Default::default(),
version: U256::zero(),
}), None).as_ref());
}
@ -2718,12 +2726,14 @@ mod tests {
code: Some(Default::default()),
storage: vec![(BigEndianHash::from_uint(&U256::from(1u64)), BigEndianHash::from_uint(&U256::from(20u64)))]
.into_iter().collect(),
version: U256::zero(),
}), Some(&PodAccount {
balance: U256::zero(),
nonce: U256::zero(),
code: Some(Default::default()),
storage: vec![(BigEndianHash::from_uint(&U256::from(1u64)), BigEndianHash::from_uint(&U256::from(100u64)))]
.into_iter().collect(),
version: U256::zero(),
})).as_ref());
}

View File

@ -57,6 +57,7 @@ fn test_blockhash_eip210(factory: Factory) {
value: ActionValue::Transfer(0.into()),
code: Some(blockhash_contract_code.clone()),
code_hash: Some(blockhash_contract_code_hash),
code_version: 0.into(),
data: Some(H256::from_low_u64_be(i - 1).as_bytes().to_vec()),
call_type: CallType::Call,
params_type: ParamsType::Separate,
@ -80,6 +81,7 @@ fn test_blockhash_eip210(factory: Factory) {
value: ActionValue::Transfer(0.into()),
code: Some(get_prev_hash_code),
code_hash: Some(get_prev_hash_code_hash),
code_version: 0.into(),
data: None,
call_type: CallType::Call,
params_type: ParamsType::Separate,

View File

@ -71,6 +71,8 @@ pub struct Account {
code_size: Option<usize>,
// Code cache of the account.
code_cache: Arc<Bytes>,
// Version of the account.
code_version: U256,
// Account code new or has been modified.
code_filth: Filth,
// Cached address hash.
@ -89,6 +91,7 @@ impl From<BasicAccount> for Account {
code_hash: basic.code_hash,
code_size: None,
code_cache: Arc::new(vec![]),
code_version: basic.code_version,
code_filth: Filth::Clean,
address_hash: Cell::new(None),
}
@ -98,7 +101,7 @@ impl From<BasicAccount> for Account {
impl Account {
#[cfg(test)]
/// General constructor.
pub fn new(balance: U256, nonce: U256, storage: HashMap<H256, H256>, code: Bytes) -> Account {
pub fn new(balance: U256, nonce: U256, storage: HashMap<H256, H256>, code: Bytes, version: U256) -> Account {
Account {
balance: balance,
nonce: nonce,
@ -109,6 +112,7 @@ impl Account {
code_hash: keccak(&code),
code_size: Some(code.len()),
code_cache: Arc::new(code),
code_version: version,
code_filth: Filth::Dirty,
address_hash: Cell::new(None),
}
@ -131,6 +135,7 @@ impl Account {
code_filth: Filth::Dirty,
code_size: Some(pod.code.as_ref().map_or(0, |c| c.len())),
code_cache: Arc::new(pod.code.map_or_else(|| { warn!("POD account with unknown code is being created! Assuming no code."); vec![] }, |c| c)),
code_version: pod.version,
address_hash: Cell::new(None),
}
}
@ -146,6 +151,7 @@ impl Account {
m
}),
code: self.code().map(|x| x.to_vec()),
version: self.code_version,
}
}
@ -161,6 +167,7 @@ impl Account {
code_hash: KECCAK_EMPTY,
code_cache: Arc::new(vec![]),
code_size: Some(0),
code_version: U256::zero(),
code_filth: Filth::Clean,
address_hash: Cell::new(None),
}
@ -174,7 +181,7 @@ impl Account {
/// Create a new contract account.
/// NOTE: make sure you use `init_code` on this before `commit`ing.
pub fn new_contract(balance: U256, nonce: U256, original_storage_root: H256) -> Account {
pub fn new_contract(balance: U256, nonce: U256, version: U256, original_storage_root: H256) -> Account {
Account {
balance: balance,
nonce: nonce,
@ -189,6 +196,7 @@ impl Account {
code_hash: KECCAK_EMPTY,
code_cache: Arc::new(vec![]),
code_size: None,
code_version: version,
code_filth: Filth::Clean,
address_hash: Cell::new(None),
}
@ -319,6 +327,9 @@ impl Account {
/// return the nonce associated with this account.
pub fn nonce(&self) -> &U256 { &self.nonce }
/// return the code version associated with this account.
pub fn code_version(&self) -> &U256 { &self.code_version }
/// return the code hash associated with this account.
pub fn code_hash(&self) -> H256 {
self.code_hash.clone()
@ -529,12 +540,15 @@ impl Account {
/// Export to RLP.
pub fn rlp(&self) -> Bytes {
let mut stream = RlpStream::new_list(4);
stream.append(&self.nonce);
stream.append(&self.balance);
stream.append(&self.storage_root);
stream.append(&self.code_hash);
stream.out()
let basic = BasicAccount {
nonce: self.nonce,
balance: self.balance,
storage_root: self.storage_root,
code_hash: self.code_hash,
code_version: self.code_version,
};
rlp::encode(&basic)
}
/// Clone basic account data
@ -549,6 +563,7 @@ impl Account {
code_hash: self.code_hash.clone(),
code_size: self.code_size.clone(),
code_cache: self.code_cache.clone(),
code_version: self.code_version,
code_filth: self.code_filth,
address_hash: self.address_hash.clone(),
}
@ -579,6 +594,7 @@ impl Account {
self.code_filth = other.code_filth;
self.code_cache = other.code_cache;
self.code_size = other.code_size;
self.code_version = other.code_version;
self.address_hash = other.address_hash;
if self.storage_root == other.storage_root {
let mut cache = self.storage_cache.borrow_mut();
@ -649,7 +665,7 @@ mod tests {
let mut db = new_memory_db();
let mut db = AccountDBMut::from_hash(&mut db, keccak(&Address::zero()));
let rlp = {
let mut a = Account::new_contract(69.into(), 0.into(), KECCAK_NULL_RLP);
let mut a = Account::new_contract(69.into(), 0.into(), 0.into(), KECCAK_NULL_RLP);
a.set_storage(H256::zero(), H256::from_low_u64_be(0x1234));
a.commit_storage(&Default::default(), &mut db).unwrap();
a.init_code(vec![]);
@ -669,7 +685,7 @@ mod tests {
let mut db = AccountDBMut::from_hash(&mut db, keccak(&Address::zero()));
let rlp = {
let mut a = Account::new_contract(69.into(), 0.into(), KECCAK_NULL_RLP);
let mut a = Account::new_contract(69.into(), 0.into(), 0.into(), KECCAK_NULL_RLP);
a.init_code(vec![0x55, 0x44, 0xffu8]);
a.commit_code(&mut db);
a.rlp()
@ -684,7 +700,7 @@ mod tests {
#[test]
fn commit_storage() {
let mut a = Account::new_contract(69.into(), 0.into(), KECCAK_NULL_RLP);
let mut a = Account::new_contract(69.into(), 0.into(), 0.into(), KECCAK_NULL_RLP);
let mut db = new_memory_db();
let mut db = AccountDBMut::from_hash(&mut db, keccak(&Address::zero()));
a.set_storage(H256::from_low_u64_be(0), H256::from_low_u64_be(0x1234));
@ -695,7 +711,7 @@ mod tests {
#[test]
fn commit_remove_commit_storage() {
let mut a = Account::new_contract(69.into(), 0.into(), KECCAK_NULL_RLP);
let mut a = Account::new_contract(69.into(), 0.into(), 0.into(), KECCAK_NULL_RLP);
let mut db = new_memory_db();
let mut db = AccountDBMut::from_hash(&mut db, keccak(&Address::zero()));
a.set_storage(H256::from_low_u64_be(0), H256::from_low_u64_be(0x1234));
@ -709,7 +725,7 @@ mod tests {
#[test]
fn commit_code() {
let mut a = Account::new_contract(69.into(), 0.into(), KECCAK_NULL_RLP);
let mut a = Account::new_contract(69.into(), 0.into(), 0.into(), KECCAK_NULL_RLP);
let mut db = new_memory_db();
let mut db = AccountDBMut::from_hash(&mut db, keccak(&Address::zero()));
a.init_code(vec![0x55, 0x44, 0xffu8]);
@ -721,7 +737,7 @@ mod tests {
#[test]
fn reset_code() {
let mut a = Account::new_contract(69.into(), 0.into(), KECCAK_NULL_RLP);
let mut a = Account::new_contract(69.into(), 0.into(), 0.into(), KECCAK_NULL_RLP);
let mut db = new_memory_db();
let mut db = AccountDBMut::from_hash(&mut db, keccak(&Address::zero()));
a.init_code(vec![0x55, 0x44, 0xffu8]);
@ -737,7 +753,7 @@ mod tests {
#[test]
fn rlpio() {
let a = Account::new(69u8.into(), 0u8.into(), HashMap::new(), Bytes::new());
let a = Account::new(69u8.into(), 0u8.into(), HashMap::new(), Bytes::new(), 0.into());
let b = Account::from_rlp(&a.rlp()).unwrap();
assert_eq!(a.balance(), b.balance());
assert_eq!(a.nonce(), b.nonce());
@ -747,7 +763,7 @@ mod tests {
#[test]
fn new_account() {
let a = Account::new(69u8.into(), 0u8.into(), HashMap::new(), Bytes::new());
let a = Account::new(69u8.into(), 0u8.into(), HashMap::new(), Bytes::new(), 0.into());
assert_eq!(a.rlp().to_hex(), "f8448045a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470");
assert_eq!(*a.balance(), 69u8.into());
assert_eq!(*a.nonce(), 0u8.into());

View File

@ -19,7 +19,7 @@
use ethereum_types::{U256, H256};
/// Basic account type.
#[derive(Debug, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BasicAccount {
/// Nonce of the account.
pub nonce: U256,
@ -29,4 +29,48 @@ pub struct BasicAccount {
pub storage_root: H256,
/// Code hash of the account.
pub code_hash: H256,
/// Code version of the account.
pub code_version: U256,
}
impl rlp::Encodable for BasicAccount {
fn rlp_append(&self, stream: &mut rlp::RlpStream) {
let use_short_version = self.code_version == U256::zero();
match use_short_version {
true => { stream.begin_list(4); }
false => { stream.begin_list(5); }
}
stream.append(&self.nonce);
stream.append(&self.balance);
stream.append(&self.storage_root);
stream.append(&self.code_hash);
if !use_short_version {
stream.append(&self.code_version);
}
}
}
impl rlp::Decodable for BasicAccount {
fn decode(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
let use_short_version = match rlp.item_count()? {
4 => true,
5 => false,
_ => return Err(rlp::DecoderError::RlpIncorrectListLen),
};
Ok(BasicAccount {
nonce: rlp.val_at(0)?,
balance: rlp.val_at(1)?,
storage_root: rlp.val_at(2)?,
code_hash: rlp.val_at(3)?,
code_version: if use_short_version {
U256::zero()
} else {
rlp.val_at(4)?
},
})
}
}

View File

@ -84,6 +84,8 @@ pub struct ActionParams {
pub value: ActionValue,
/// Code being executed.
pub code: Option<Arc<Bytes>>,
/// Code version being executed.
pub code_version: U256,
/// Input data.
pub data: Option<Bytes>,
/// Type of call
@ -105,6 +107,7 @@ impl Default for ActionParams {
gas_price: U256::zero(),
value: ActionValue::Transfer(U256::zero()),
code: None,
code_version: U256::zero(),
data: None,
call_type: CallType::None,
params_type: ParamsType::Separate,
@ -122,6 +125,7 @@ impl From<ethjson::vm::Transaction> for ActionParams {
sender: t.sender.into(),
origin: t.origin.into(),
code: Some(Arc::new(t.code.into())),
code_version: t.code_version.into(),
data: Some(t.data.into()),
gas: t.gas.into(),
gas_price: t.gas_price.into(),

View File

@ -97,6 +97,7 @@ pub trait Ext {
gas: &U256,
value: &U256,
code: &[u8],
parent_version: &U256,
address: CreateContractAddress,
trap: bool,
) -> ::std::result::Result<ContractCreateResult, TrapKind>;

View File

@ -36,7 +36,7 @@ pub mod tests;
pub use action_params::{ActionParams, ActionValue, ParamsType};
pub use call_type::CallType;
pub use env_info::{EnvInfo, LastHashes};
pub use schedule::{Schedule, CleanDustMode, WasmCosts};
pub use schedule::{Schedule, VersionedSchedule, CleanDustMode, WasmCosts};
pub use ext::{Ext, MessageCallResult, ContractCreateResult, CreateContractAddress};
pub use return_data::{ReturnData, GasLeft};
pub use error::{Error, Result, TrapResult, TrapError, TrapKind, ExecTrapResult, ExecTrapError};

View File

@ -15,6 +15,13 @@
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
//! Cost schedule and other parameterisations for the EVM.
use std::collections::HashMap;
use ethereum_types::U256;
/// Definition of schedules that can be applied to a version.
pub enum VersionedSchedule {
PWasm,
}
/// Definition of the cost schedule and other parameterisations for the EVM.
pub struct Schedule {
@ -121,6 +128,10 @@ pub struct Schedule {
pub eip1283: bool,
/// VM execution does not increase null signed address nonce if this field is true.
pub keep_unsigned_nonce: bool,
/// Latest VM version for contract creation transaction.
pub latest_version: U256,
/// All supported non-legacy VM versions.
pub versions: HashMap<U256, VersionedSchedule>,
/// Wasm extra schedule settings, if wasm activated
pub wasm: Option<WasmCosts>,
}
@ -254,6 +265,8 @@ impl Schedule {
kill_dust: CleanDustMode::Off,
eip1283: false,
keep_unsigned_nonce: false,
latest_version: U256::zero(),
versions: HashMap::new(),
wasm: None,
}
}
@ -328,6 +341,8 @@ impl Schedule {
kill_dust: CleanDustMode::Off,
eip1283: false,
keep_unsigned_nonce: false,
latest_version: U256::zero(),
versions: HashMap::new(),
wasm: None,
}
}

View File

@ -144,6 +144,7 @@ impl Ext for FakeExt {
gas: &U256,
value: &U256,
code: &[u8],
_parent_version: &U256,
address: CreateContractAddress,
_trap: bool,
) -> ::std::result::Result<ContractCreateResult, TrapKind> {

View File

@ -131,6 +131,7 @@ impl WasmInterpreter {
sender: self.params.sender,
origin: self.params.origin,
code_address: self.params.code_address,
code_version: self.params.code_version,
value: self.params.value.value(),
},
);

View File

@ -25,6 +25,7 @@ pub struct RuntimeContext {
pub sender: Address,
pub origin: Address,
pub code_address: Address,
pub code_version: U256,
pub value: U256,
}
@ -529,7 +530,7 @@ impl<'a> Runtime<'a> {
* U256::from(self.ext.schedule().wasm().opcodes_mul)
/ U256::from(self.ext.schedule().wasm().opcodes_div);
match self.ext.create(&gas_left, &endowment, &code, scheme, false).ok().expect("Trap is false; trap error will not happen; qed") {
match self.ext.create(&gas_left, &endowment, &code, &self.context.code_version, scheme, false).ok().expect("Trap is false; trap error will not happen; qed") {
vm::ContractCreateResult::Created(address, gas_left) => {
self.memory.set(result_ptr, address.as_bytes())?;
self.gas_counter = self.gas_limit -

View File

@ -31,6 +31,9 @@ pub struct Account {
pub nonce: Uint,
/// Storage.
pub storage: BTreeMap<Uint, Uint>,
/// Version.
#[serde(default)]
pub version: Uint,
}
#[cfg(test)]

View File

@ -33,6 +33,8 @@ pub struct Account {
pub nonce: Option<Uint>,
/// Code.
pub code: Option<Bytes>,
/// Version.
pub version: Option<Uint>,
/// Storage.
pub storage: Option<BTreeMap<Uint, Uint>>,
/// Constructor.

View File

@ -118,8 +118,10 @@ pub struct Params {
pub transaction_permission_contract: Option<Address>,
/// Block at which the transaction permission contract should start being used.
pub transaction_permission_contract_transition: Option<Uint>,
/// Wasm activation block height, if not activated from start
/// Wasm activation block height, if not activated from start.
pub wasm_activation_transition: Option<Uint>,
/// Define a separate wasm version instead of using the prefix.
pub wasm_version: Option<Uint>,
/// KIP4 activiation block height.
pub kip4_transition: Option<Uint>,
/// KIP6 activiation block height.

View File

@ -40,6 +40,9 @@ pub struct Transaction {
pub origin: Address,
/// Sent value.
pub value: Uint,
/// Contract code version.
#[serde(default)]
pub code_version: Uint,
}
#[cfg(test)]