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

Staking Precompiles #358

Merged
merged 102 commits into from
May 3, 2021
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
832726b
init
4meta5 Apr 8, 2021
f179e91
Merge branch 'master' into amar-int-tests
4meta5 Apr 12, 2021
737c0fd
save
4meta5 Apr 13, 2021
cf4e42d
Initial pass at Sacrifice precompile
notlesh Apr 13, 2021
9efad64
Add gas-related tests for Sacrifice precompile
notlesh Apr 13, 2021
69a32b7
import pallet instances from runtime and start evm transfer to stake …
4meta5 Apr 14, 2021
71d5660
transfer to stake without evm transfer
4meta5 Apr 14, 2021
1f10d75
rename carl to charlie
4meta5 Apr 14, 2021
97fc054
push failed test to show how evm call works
4meta5 Apr 15, 2021
688a324
set gas price and limit to make test pass
JoshOrndorff Apr 15, 2021
b3abf42
test still fails, push to show that transfer through EVM does not suc…
4meta5 Apr 15, 2021
f154f50
test passes
4meta5 Apr 15, 2021
270ea2d
save unfinished pay block authors todo
4meta5 Apr 15, 2021
ad59685
Sketched idea, mostly right (I think), doesn't compile because of .di…
JoshOrndorff Apr 15, 2021
0d2a184
holy shit it compiles!
JoshOrndorff Apr 15, 2021
a539aaa
ditch BigUint
JoshOrndorff Apr 15, 2021
1e5f8d1
don't need to name phantom field
JoshOrndorff Apr 15, 2021
28a7e50
BalanceOf<Runtime>
JoshOrndorff Apr 15, 2021
ab7fea5
Don't need temp buffers for inputs
JoshOrndorff Apr 15, 2021
026d4c9
no_std
JoshOrndorff Apr 15, 2021
fbb9852
reward block authors runtime integration test
4meta5 Apr 16, 2021
15aae62
improve readability
4meta5 Apr 18, 2021
a46d404
increase test inflation config to see test balances go up more
4meta5 Apr 18, 2021
106beec
move to own file, and rename for pallet-specific precompile
JoshOrndorff Apr 20, 2021
17ebf9b
Try (and fail) to install in Runtime
JoshOrndorff Apr 20, 2021
93b197c
Add proper trait bounds so it compiles
JoshOrndorff Apr 20, 2021
4d8838e
Better constant use
JoshOrndorff Apr 20, 2021
5f9a279
Prep work for ABI stuff
JoshOrndorff Apr 20, 2021
e543abb
oops
JoshOrndorff Apr 20, 2021
6c5b3b3
Try some logging - Can't figure out why calls are failing
JoshOrndorff Apr 20, 2021
d5c24c7
Merge branch 'master' into joshy-staking-precompiles
JoshOrndorff Apr 20, 2021
481f43d
address most review comments
4meta5 Apr 21, 2021
84789de
Idea: don't be generic?
JoshOrndorff Apr 21, 2021
3ff424f
Merge branch 'master' into joshy-staking-precompiles
JoshOrndorff Apr 21, 2021
f2660f3
Merge branch 'amar-int-tests' into joshy-staking-precompiles
JoshOrndorff Apr 21, 2021
883a892
Another cleanup idea.
JoshOrndorff Apr 21, 2021
ca44e43
Merge branch 'master' into amar-int-tests
JoshOrndorff Apr 21, 2021
9e6c7f8
Fix test for root origin
JoshOrndorff Apr 21, 2021
eba6070
Merge branch 'amar-int-tests' into joshy-staking-precompiles
JoshOrndorff Apr 21, 2021
b12d12e
Slightly clean the logging even though it isn't working
JoshOrndorff Apr 21, 2021
78e46ab
sketch a test
JoshOrndorff Apr 21, 2021
d0d988e
idea about how to get big endial nomination amount vec
JoshOrndorff Apr 21, 2021
d4ec7ee
Print events more nicely
JoshOrndorff Apr 21, 2021
0f08b90
Seemed to work
JoshOrndorff Apr 21, 2021
821988e
Clean up address
JoshOrndorff Apr 21, 2021
6b2da9b
Cleanup test init
JoshOrndorff Apr 21, 2021
6fc8583
Cleanup test init
JoshOrndorff Apr 21, 2021
3c1ca8a
Nicer balances format
JoshOrndorff Apr 21, 2021
ee0cd4c
remove prints
JoshOrndorff Apr 21, 2021
4367b09
Start sketching toward a second wrapped extrinsic
JoshOrndorff Apr 21, 2021
6a61969
Fix test
JoshOrndorff Apr 21, 2021
189a204
matching on selector is working
JoshOrndorff Apr 22, 2021
5e0c1ee
precompile and test for join candidates
JoshOrndorff Apr 22, 2021
0e69a5e
finish precompiles
4meta5 Apr 22, 2021
0ac505c
fmt
4meta5 Apr 22, 2021
9f151cf
fmt
4meta5 Apr 22, 2021
d184ab9
green
4meta5 Apr 22, 2021
e9c6bd0
more tests
4meta5 Apr 22, 2021
11a3d68
finish tests
4meta5 Apr 22, 2021
810de06
better
4meta5 Apr 22, 2021
e0f0eef
sketch example contract
JoshOrndorff Apr 22, 2021
eff5128
Fill in precompile calls. Untested, but solc accepts them :)
JoshOrndorff Apr 22, 2021
140dbbd
accessor selector
JoshOrndorff Apr 22, 2021
5461907
attempt at integration test
JoshOrndorff Apr 22, 2021
8cdd26e
better logging
JoshOrndorff Apr 23, 2021
28fbe48
switch to interface
JoshOrndorff Apr 23, 2021
fa41eb3
Reminder stopped vs returned
JoshOrndorff Apr 23, 2021
a39da02
enable receive function
JoshOrndorff Apr 23, 2021
5e8374c
More logging
JoshOrndorff Apr 23, 2021
d8da583
Simpler test contract
JoshOrndorff Apr 23, 2021
1f3bedb
skip tests so docker image builds
JoshOrndorff Apr 23, 2021
32bb16f
added test for staking precompile
joelamouche Apr 23, 2021
e7eed6e
More logging, notes, and cleanup from learning session
JoshOrndorff Apr 23, 2021
8197789
Add some bogus code predoplyed under precompile
JoshOrndorff Apr 23, 2021
083a1c3
Merge branch 'master' into joshy-staking-precompiles
4meta5 Apr 24, 2021
262aef0
Slightly nicer predeploy
JoshOrndorff Apr 26, 2021
0a216c2
sketch idea to fetch addresses from moonbeam precompile struct
JoshOrndorff Apr 26, 2021
f44f96b
accessor attempt compiles
JoshOrndorff Apr 26, 2021
68eb3db
First access test passes
JoshOrndorff Apr 26, 2021
20e951d
Consider 12 mystery bytes when calling accessor + test
JoshOrndorff Apr 27, 2021
c109b36
Narrower generic params
JoshOrndorff Apr 27, 2021
2da7a9d
helper method
JoshOrndorff Apr 27, 2021
009d688
cleaner matching
JoshOrndorff Apr 27, 2021
24da4bf
More complete interface including accessor methods
JoshOrndorff Apr 27, 2021
cedc825
compare gases instead of weights
JoshOrndorff Apr 27, 2021
c3fc676
moar logging
JoshOrndorff Apr 27, 2021
06b2fdd
💡 EVERYTHING is padded to 256 bits
JoshOrndorff Apr 27, 2021
89675f1
strip too-verbose logging from precompile set
JoshOrndorff Apr 27, 2021
49bb673
properly parse account in recoke_nomination
JoshOrndorff Apr 27, 2021
fd8c7bc
Basic nomination dao is working
JoshOrndorff Apr 27, 2021
898f02c
More polishing on Nomination DAO
JoshOrndorff Apr 27, 2021
ff4e4c9
remove sacrifice precompile stuff and make all integration tests pass…
4meta5 Apr 28, 2021
91b4dfb
clean and move unfinished tests to jira moonbeam 495
4meta5 Apr 28, 2021
b8842b2
green
4meta5 Apr 28, 2021
ec9ca76
Merge branch 'master' into joshy-staking-precompiles
4meta5 Apr 29, 2021
dd33e71
add is candidate with rust runtime integration test
4meta5 Apr 29, 2021
f2d6443
clean and bump spec version
4meta5 Apr 29, 2021
d116efb
address some review comments
4meta5 Apr 29, 2021
97c9436
min nomination test commented out but fails
4meta5 Apr 30, 2021
cb0addd
add some comments requested in review
4meta5 Apr 30, 2021
9557521
fix min nomination test as per joshy comment
4meta5 Apr 30, 2021
b8f3122
fix precompile addresses in dead code
4meta5 Apr 30, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 9 additions & 1 deletion runtime/precompiles/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,20 @@ frame-support = { git = "https://github.com/paritytech/substrate", branch = "roc
evm = { version = "0.26.0", default-features = false, features = ["with-codec"] }
sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-v1" }
sp-core = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-v1" }
sp-io = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-v1" }
pallet-evm = { git = "https://github.com/purestake/frontier", default-features = false, branch = "notlesh-moonbeam-v0.7" }
pallet-evm-precompile-bn128 = { git = "https://github.com/purestake/frontier", default-features = false, branch = "notlesh-moonbeam-v0.7" }
pallet-evm-precompile-dispatch = { git = "https://github.com/purestake/frontier", default-features = false, branch = "notlesh-moonbeam-v0.7" }
pallet-evm-precompile-modexp = { git = "https://github.com/purestake/frontier", default-features = false, branch = "notlesh-moonbeam-v0.7" }
pallet-evm-precompile-simple = { git = "https://github.com/purestake/frontier", default-features = false, branch = "notlesh-moonbeam-v0.7" }
parachain-staking = { path = "../../pallets/parachain-staking", default-features = false }
frame-system = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "rococo-v1" }

[dev-dependencies]
hex = "0.4"

[features]
default = [ "std" ]
default = ["std"]
std = [
"codec/std",
"frame-support/std",
Expand All @@ -35,4 +41,6 @@ std = [
"pallet-evm-precompile-dispatch/std",
"pallet-evm-precompile-modexp/std",
"pallet-evm-precompile-simple/std",
"parachain-staking/std",
"frame-system/std",
]
274 changes: 274 additions & 0 deletions runtime/precompiles/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,170 @@

#![cfg_attr(not(feature = "std"), no_std)]

use std::u64;

use codec::Decode;
use evm::{Context, ExitError, ExitSucceed};
use frame_support::dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo};
use frame_support::traits::Currency;
use pallet_evm::AddressMapping;
use pallet_evm::GasWeightMapping;
use pallet_evm::{Config, Precompile, PrecompileSet};
use pallet_evm_precompile_bn128::{Bn128Add, Bn128Mul, Bn128Pairing};
use pallet_evm_precompile_dispatch::Dispatch;
use pallet_evm_precompile_modexp::Modexp;
use pallet_evm_precompile_simple::{ECRecover, Identity, Ripemd160, Sha256};
use sp_core::offchain::Duration;
use sp_core::H160;
use sp_core::U256;
use sp_io::offchain;
use sp_std::convert::TryFrom;
use sp_std::convert::TryInto;
use sp_std::fmt::Debug;
use sp_std::{marker::PhantomData, vec::Vec};

/// A precompile intended to burn gas and/or time without actually doing any work.
/// Meant for testing.
///
/// Expects call data to include two u64 values:
/// 1) The gas to sacrifice (charge)
/// 2) The time in msec to sleep
/// TODO: use feature flags / somehow prevent this from deployment onto live networks (or not?)
struct Sacrifice;

impl Precompile for Sacrifice {
fn execute(
input: &[u8],
target_gas: Option<u64>,
_context: &Context,
) -> core::result::Result<(ExitSucceed, Vec<u8>, u64), ExitError> {
const INPUT_SIZE_BYTES: usize = 16;

// input should be exactly 16 bytes (two 8-byte unsigned ints in big endian)
if input.len() != INPUT_SIZE_BYTES {
return Err(ExitError::Other(
"input length for Sacrifice must be exactly 16 bytes".into(),
));
}

// create 8-byte buffers and populate them from calldata...
let mut gas_cost_buf: [u8; 8] = [0; 8];
let mut msec_cost_buf: [u8; 8] = [0; 8];

gas_cost_buf.copy_from_slice(&input[0..8]);
msec_cost_buf.copy_from_slice(&input[8..16]);

// then read them into a u64 as big-endian...
let gas_cost = u64::from_be_bytes(gas_cost_buf);
let msec_cost = u64::from_be_bytes(msec_cost_buf);

// ensure we can afford our sacrifice...
if let Some(gas_left) = target_gas {
if gas_left < gas_cost {
return Err(ExitError::OutOfGas);
}
}

// TODO: impose gas-per-second constraint?

if msec_cost > 0 {
// TODO: log statement here
let deadline = offchain::timestamp();
deadline.add(Duration::from_millis(msec_cost));
offchain::sleep_until(deadline);
}

// TODO Should this actually be stopped?
// https://ethervm.io/#F3
// REvisit: does solidity void contract issue stop or return
Ok((ExitSucceed::Returned, [0u8; 0].to_vec(), gas_cost))
}
}

/// A precompile to wrap the functionality from parachain_staking::nominate.
///
/// EXAMPLE USECASE:
/// A simple example usecase is a contract that allows donors to donate, and stakes all the funds
/// toward one fixed address chosen by the deployer.
/// Such a contract could be deployed by a collator candidate, and the deploy address distributed to
/// supporters who want to donate toward a perpetual nomination fund.
pub struct NominateWrapper<Runtime>(PhantomData<Runtime>);

type BalanceOf<Runtime> = <<Runtime as parachain_staking::Config>::Currency as Currency<
<Runtime as frame_system::Config>::AccountId,
>>::Balance;

impl<Runtime> Precompile for NominateWrapper<Runtime>
where
Runtime: parachain_staking::Config + pallet_evm::Config,
Runtime::AccountId: From<H160>,
BalanceOf<Runtime>: TryFrom<U256> + Debug,
Runtime::Call: Dispatchable<PostInfo = PostDispatchInfo> + GetDispatchInfo,
<Runtime::Call as Dispatchable>::Origin: From<Option<Runtime::AccountId>>,
Runtime::Call: From<parachain_staking::Call<Runtime>>,
{
fn execute(
input: &[u8], //Reminder this is big-endian
target_gas: Option<u64>,
context: &Context,
) -> Result<(ExitSucceed, Vec<u8>, u64), ExitError> {
// Basic sanity checking for length
// https://solidity-by-example.org/primitives/
const COLLATOR_SIZE_BYTES: usize = 20;
const AMOUNT_SIZE_BYTES: usize = 32;
const TOTAL_SIZE_BYTES: usize = COLLATOR_SIZE_BYTES + AMOUNT_SIZE_BYTES;

if input.len() != COLLATOR_SIZE_BYTES + AMOUNT_SIZE_BYTES {
return Err(ExitError::Other(
"input length for Sacrifice must be exactly 16 bytes".into(),
));
}

// Convert to right data types
//TODO I guess we should use the account mapping if possible. Otherwise this won't work for
// chains tht have a more standard substrate account type.
let collator = H160::from_slice(&input[0..COLLATOR_SIZE_BYTES]);
JoshOrndorff marked this conversation as resolved.
Show resolved Hide resolved

let amount: BalanceOf<Runtime> =
sp_core::U256::from_big_endian(&input[COLLATOR_SIZE_BYTES..TOTAL_SIZE_BYTES])
.try_into()
.map_err(|_| {
ExitError::Other("amount is too large for Runtime's balance type".into())
})?;

println!("Collator account is {:?}", collator);
println!("Amount is {:?}", amount);

// Construct a call
let inner_call = parachain_staking::Call::<Runtime>::nominate(collator.into(), amount);
let outer_call: Runtime::Call = inner_call.into();
let info = outer_call.get_dispatch_info();

// Make sure enough gas
if let Some(gas_limit) = target_gas {
let valid_weight = info.weight <= Runtime::GasWeightMapping::gas_to_weight(gas_limit);
JoshOrndorff marked this conversation as resolved.
Show resolved Hide resolved
if !valid_weight {
return Err(ExitError::OutOfGas);
}
}

// Dispatch that call
let origin = Runtime::AddressMapping::into_account_id(context.caller);

match outer_call.dispatch(Some(origin).into()) {
Ok(post_info) => {
let gas_used = Runtime::GasWeightMapping::weight_to_gas(
post_info.actual_weight.unwrap_or(info.weight),
);
Ok((ExitSucceed::Stopped, Default::default(), gas_used))
}
Err(_) => Err(ExitError::Other(
"Parachain staking nomination failed".into(),
)),
}
}
}

/// The PrecompileSet installed in the Moonbeam runtime.
/// We include the nine Istanbul precompiles
/// (https://github.com/ethereum/go-ethereum/blob/3c46f557/core/vm/contracts.go#L69)
Expand Down Expand Up @@ -58,6 +211,8 @@ where
a if a == hash(8) => Some(Bn128Pairing::execute(input, target_gas, context)),
// Moonbeam precompiles :
a if a == hash(255) => Some(Dispatch::<R>::execute(input, target_gas, context)),
// Moonbeam testing-only precompile(s):
a if a == hash(511) => Some(Sacrifice::execute(input, target_gas, context)),
_ => None,
}
}
Expand All @@ -66,3 +221,122 @@ where
fn hash(a: u64) -> H160 {
H160::from_low_u64_be(a)
}

#[cfg(test)]
mod tests {
use super::*;
use std::time::{Duration, Instant};
// use sp_io::TestExternalities; XXX
extern crate hex;

/*
* XXX
pub fn new_test_ext() -> TestExternalities {
let t = frame_system::GenesisConfig::default()
.build_storage::<Test>()
.unwrap();
TestExternalities::new(t)
}
*/

#[test]
fn test_invalid_input_length() -> std::result::Result<(), ExitError> {
let cost: u64 = 1;

// TODO: this is very not-DRY, it would be nice to have a default / test impl in Frontier
let context: Context = Context {
address: Default::default(),
caller: Default::default(),
apparent_value: From::from(0),
};

// should fail with input of 15 byte length
let input: [u8; 15] = [0; 15];
assert_eq!(
Sacrifice::execute(&input, Some(cost), &context),
Err(ExitError::Other(
"input length for Sacrifice must be exactly 16 bytes".into()
)),
);

// should fail with input of 17 byte length
let input: [u8; 17] = [0; 17];
assert_eq!(
Sacrifice::execute(&input, Some(cost), &context),
Err(ExitError::Other(
"input length for Sacrifice must be exactly 16 bytes".into()
)),
);

Ok(())
}

#[test]
fn test_gas_consumption() -> std::result::Result<(), ExitError> {
let mut input: [u8; 16] = [0; 16];
input[..8].copy_from_slice(&123456_u64.to_be_bytes());

let context: Context = Context {
address: Default::default(),
caller: Default::default(),
apparent_value: From::from(0),
};

assert_eq!(
Sacrifice::execute(&input, None, &context),
Ok((ExitSucceed::Returned, [0u8; 0].to_vec(), 123456)),
);

Ok(())
}

#[test]
fn test_oog() -> std::result::Result<(), ExitError> {
let mut input: [u8; 16] = [0; 16];
input[..8].copy_from_slice(&100_u64.to_be_bytes());

let context: Context = Context {
address: Default::default(),
caller: Default::default(),
apparent_value: From::from(0),
};

assert_eq!(
Sacrifice::execute(&input, Some(99), &context),
Err(ExitError::OutOfGas),
);

Ok(())
}

/*
* TODO: the sleep() function inside the precompile must be run within an externalities
* environment
#[test]
fn test_sleep() -> std::result::Result<(), ExitError> {

new_test_ext().execute_with(|| {
let mut input: [u8; 16] = [0; 16];
input[8..].copy_from_slice(&10_u64.to_be_bytes()); // should be 10ms

let context: Context = Context {
address: Default::default(),
caller: Default::default(),
apparent_value: From::from(0),
};

let start = Instant::now();

assert_eq!(
Sacrifice::execute(&input, Some(99), &context),
Ok((ExitSucceed::Returned, [0u8; 0].to_vec(), 0)),
);

assert!(start.elapsed().as_millis() > 10);
assert!(start.elapsed().as_millis() < 20); // give plenty of room, but put some bound on it

Ok(())
});
}
*/
}