mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-31 02:51:01 +00:00
contracts: add sr25519_verify (#13724)
* wip * fix * wip * fix lint * rm fixture fix * missing comment * fix lint * add comment to the wsm file * fix comment * Apply suggestions from code review Co-authored-by: Sasha Gryaznov <hi@agryaznov.com> * wip * wip weights * wip weights * PR comment: test with return code * wip * PR review add mock test * remove * lint * Update frame/contracts/fixtures/sr25519_verify.wat * fix comments * Update frame/contracts/src/benchmarking/mod.rs * Update frame/contracts/src/wasm/runtime.rs * Update frame/contracts/fixtures/sr25519_verify.wat * Update frame/contracts/src/benchmarking/mod.rs * fix lint * ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts * Update frame/contracts/src/wasm/runtime.rs Co-authored-by: Alexander Theißen <alex.theissen@me.com> * PR: review use unstable + remove arbitrary index 4 * Add benchmark for calculating overhead of calling sr25519_verify * fix message length encoding * fix weights * ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts * Apply suggestions from code review * Update frame/contracts/src/wasm/runtime.rs * Update frame/contracts/src/wasm/runtime.rs * Update frame/contracts/src/benchmarking/mod.rs * Update frame/contracts/src/benchmarking/mod.rs * Update frame/contracts/src/schedule.rs Co-authored-by: Sasha Gryaznov <hi@agryaznov.com> * Update frame/contracts/src/schedule.rs Co-authored-by: Sasha Gryaznov <hi@agryaznov.com> * Update frame/contracts/src/wasm/runtime.rs * Update frame/contracts/src/wasm/runtime.rs Co-authored-by: Sasha Gryaznov <hi@agryaznov.com> * PR review --------- Co-authored-by: Sasha Gryaznov <hi@agryaznov.com> Co-authored-by: command-bot <> Co-authored-by: Alexander Theißen <alex.theissen@me.com>
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
;; This contract:
|
||||
;; 1) Reads signature, message and public key from the input
|
||||
;; 2) Calls and return the result of sr25519_verify
|
||||
|
||||
(module
|
||||
;; import the host functions from the seal0 module
|
||||
(import "seal0" "sr25519_verify" (func $sr25519_verify (param i32 i32 i32 i32) (result i32)))
|
||||
(import "seal0" "seal_input" (func $seal_input (param i32 i32)))
|
||||
(import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
|
||||
|
||||
;; give the program 1 page of memory
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
;; [0, 4) length of signature + message + public key - 64 + 11 + 32 = 107 bytes
|
||||
;; write the length of the input (6b = 107) bytes at offset 0
|
||||
(data (i32.const 0) "\6b")
|
||||
|
||||
(func (export "deploy"))
|
||||
|
||||
(func (export "call")
|
||||
;; define local variables
|
||||
(local $signature_ptr i32)
|
||||
(local $pub_key_ptr i32)
|
||||
(local $message_len i32)
|
||||
(local $message_ptr i32)
|
||||
|
||||
;; set the pointers to the memory locations
|
||||
;; Memory layout during `call`
|
||||
;; [10, 74) signature
|
||||
;; [74, 106) public key
|
||||
;; [106, 117) message (11 bytes)
|
||||
(local.set $signature_ptr (i32.const 10))
|
||||
(local.set $pub_key_ptr (i32.const 74))
|
||||
(local.set $message_ptr (i32.const 106))
|
||||
|
||||
;; store the input into the memory, starting at the signature and
|
||||
;; up to 107 bytes stored at offset 0
|
||||
(call $seal_input (local.get $signature_ptr) (i32.const 0))
|
||||
|
||||
;; call sr25519_verify and store the return code
|
||||
(i32.store
|
||||
(i32.const 0)
|
||||
(call $sr25519_verify
|
||||
(local.get $signature_ptr)
|
||||
(local.get $pub_key_ptr)
|
||||
(i32.const 11)
|
||||
(local.get $message_ptr)
|
||||
)
|
||||
)
|
||||
|
||||
;; exit with success and take transfer return code to the output buffer
|
||||
(call $seal_return (i32.const 0) (i32.const 0) (i32.const 4))
|
||||
)
|
||||
)
|
||||
|
||||
@@ -2008,9 +2008,114 @@ benchmarks! {
|
||||
let origin = RawOrigin::Signed(instance.caller.clone());
|
||||
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])
|
||||
|
||||
// `n`: Message input length to verify in bytes.
|
||||
#[pov_mode = Measured]
|
||||
seal_sr25519_verify_per_byte {
|
||||
let n in 0 .. T::MaxCodeLen::get() - 255; // need some buffer so the code size does not
|
||||
// exceed the max code size.
|
||||
|
||||
let message = (0..n).zip((32u8..127u8).cycle()).map(|(_, c)| c).collect::<Vec<_>>();
|
||||
let message_len = message.len() as i32;
|
||||
|
||||
let key_type = sp_core::crypto::KeyTypeId(*b"code");
|
||||
let pub_key = sp_io::crypto::sr25519_generate(key_type, None);
|
||||
let sig = sp_io::crypto::sr25519_sign(key_type, &pub_key, &message).expect("Generates signature");
|
||||
let sig = AsRef::<[u8; 64]>::as_ref(&sig).to_vec();
|
||||
|
||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "seal0",
|
||||
name: "sr25519_verify",
|
||||
params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32],
|
||||
return_type: Some(ValueType::I32),
|
||||
}],
|
||||
data_segments: vec![
|
||||
DataSegment {
|
||||
offset: 0,
|
||||
value: sig,
|
||||
},
|
||||
DataSegment {
|
||||
offset: 64,
|
||||
value: pub_key.to_vec(),
|
||||
},
|
||||
DataSegment {
|
||||
offset: 96,
|
||||
value: message,
|
||||
},
|
||||
],
|
||||
call_body: Some(body::plain(vec![
|
||||
Instruction::I32Const(0), // signature_ptr
|
||||
Instruction::I32Const(64), // pub_key_ptr
|
||||
Instruction::I32Const(message_len), // message_len
|
||||
Instruction::I32Const(96), // message_ptr
|
||||
Instruction::Call(0),
|
||||
Instruction::Drop,
|
||||
Instruction::End,
|
||||
])),
|
||||
.. Default::default()
|
||||
});
|
||||
|
||||
let instance = Contract::<T>::new(code, vec![])?;
|
||||
let origin = RawOrigin::Signed(instance.caller.clone());
|
||||
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])
|
||||
|
||||
// Only calling the function itself with valid arguments.
|
||||
// It generates different private keys and signatures for the message "Hello world".
|
||||
// This is a slow call: We redeuce the number of runs.
|
||||
// This is a slow call: We reduce the number of runs.
|
||||
#[pov_mode = Measured]
|
||||
seal_sr25519_verify {
|
||||
let r in 0 .. API_BENCHMARK_RUNS / 10;
|
||||
|
||||
let message = b"Hello world".to_vec();
|
||||
let message_len = message.len() as i32;
|
||||
let key_type = sp_core::crypto::KeyTypeId(*b"code");
|
||||
let sig_params = (0..r)
|
||||
.map(|i| {
|
||||
let pub_key = sp_io::crypto::sr25519_generate(key_type, None);
|
||||
let sig = sp_io::crypto::sr25519_sign(key_type, &pub_key, &message).expect("Generates signature");
|
||||
let data: [u8; 96] = [AsRef::<[u8]>::as_ref(&sig), AsRef::<[u8]>::as_ref(&pub_key)].concat().try_into().unwrap();
|
||||
data
|
||||
})
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
let sig_params_len = sig_params.len() as i32;
|
||||
|
||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "seal0",
|
||||
name: "sr25519_verify",
|
||||
params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32],
|
||||
return_type: Some(ValueType::I32),
|
||||
}],
|
||||
data_segments: vec![
|
||||
DataSegment {
|
||||
offset: 0,
|
||||
value: sig_params
|
||||
},
|
||||
DataSegment {
|
||||
offset: sig_params_len as u32,
|
||||
value: message,
|
||||
},
|
||||
],
|
||||
call_body: Some(body::repeated_dyn(r, vec![
|
||||
Counter(0, 96), // signature_ptr
|
||||
Counter(64, 96), // pub_key_ptr
|
||||
Regular(Instruction::I32Const(message_len)), // message_len
|
||||
Regular(Instruction::I32Const(sig_params_len)), // message_ptr
|
||||
Regular(Instruction::Call(0)),
|
||||
Regular(Instruction::Drop),
|
||||
])),
|
||||
.. Default::default()
|
||||
});
|
||||
let instance = Contract::<T>::new(code, vec![])?;
|
||||
let origin = RawOrigin::Signed(instance.caller.clone());
|
||||
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])
|
||||
|
||||
// Only calling the function itself with valid arguments.
|
||||
// It generates different private keys and signatures for the message "Hello world".
|
||||
// This is a slow call: We reduce the number of runs.
|
||||
#[pov_mode = Measured]
|
||||
seal_ecdsa_recover {
|
||||
let r in 0 .. API_BENCHMARK_RUNS / 10;
|
||||
|
||||
@@ -35,7 +35,10 @@ use frame_support::{
|
||||
use frame_system::RawOrigin;
|
||||
use pallet_contracts_primitives::ExecReturnValue;
|
||||
use smallvec::{Array, SmallVec};
|
||||
use sp_core::ecdsa::Public as ECDSAPublic;
|
||||
use sp_core::{
|
||||
ecdsa::Public as ECDSAPublic,
|
||||
sr25519::{Public as SR25519Public, Signature as SR25519Signature},
|
||||
};
|
||||
use sp_io::{crypto::secp256k1_ecdsa_recover_compressed, hashing::blake2_256};
|
||||
use sp_runtime::traits::{Convert, Hash};
|
||||
use sp_std::{marker::PhantomData, mem, prelude::*, vec::Vec};
|
||||
@@ -272,6 +275,9 @@ pub trait Ext: sealing::Sealed {
|
||||
/// Recovers ECDSA compressed public key based on signature and message hash.
|
||||
fn ecdsa_recover(&self, signature: &[u8; 65], message_hash: &[u8; 32]) -> Result<[u8; 33], ()>;
|
||||
|
||||
/// Verify a sr25519 signature.
|
||||
fn sr25519_verify(&self, signature: &[u8; 64], message: &[u8], pub_key: &[u8; 32]) -> bool;
|
||||
|
||||
/// Returns Ethereum address from the ECDSA compressed public key.
|
||||
fn ecdsa_to_eth_address(&self, pk: &[u8; 33]) -> Result<[u8; 20], ()>;
|
||||
|
||||
@@ -1347,6 +1353,14 @@ where
|
||||
secp256k1_ecdsa_recover_compressed(signature, message_hash).map_err(|_| ())
|
||||
}
|
||||
|
||||
fn sr25519_verify(&self, signature: &[u8; 64], message: &[u8], pub_key: &[u8; 32]) -> bool {
|
||||
sp_io::crypto::sr25519_verify(
|
||||
&SR25519Signature(*signature),
|
||||
message,
|
||||
&SR25519Public(*pub_key),
|
||||
)
|
||||
}
|
||||
|
||||
fn ecdsa_to_eth_address(&self, pk: &[u8; 33]) -> Result<[u8; 20], ()> {
|
||||
ECDSAPublic(*pk).to_eth_address()
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ impl Limits {
|
||||
/// Describes the weight for all categories of supported wasm instructions.
|
||||
///
|
||||
/// There there is one field for each wasm instruction that describes the weight to
|
||||
/// execute one instruction of that name. There are a few execptions:
|
||||
/// execute one instruction of that name. There are a few exceptions:
|
||||
///
|
||||
/// 1. If there is a i64 and a i32 variant of an instruction we use the weight
|
||||
/// of the former for both.
|
||||
@@ -409,6 +409,12 @@ pub struct HostFnWeights<T: Config> {
|
||||
/// Weight of calling `seal_ecdsa_to_eth_address`.
|
||||
pub ecdsa_to_eth_address: Weight,
|
||||
|
||||
/// Weight of calling `sr25519_verify`.
|
||||
pub sr25519_verify: Weight,
|
||||
|
||||
/// Weight per byte of calling `sr25519_verify`.
|
||||
pub sr25519_verify_per_byte: Weight,
|
||||
|
||||
/// Weight of calling `reentrance_count`.
|
||||
pub reentrance_count: Weight,
|
||||
|
||||
@@ -616,6 +622,8 @@ impl<T: Config> Default for HostFnWeights<T> {
|
||||
hash_blake2_128: cost!(seal_hash_blake2_128),
|
||||
hash_blake2_128_per_byte: cost!(seal_hash_blake2_128_per_byte),
|
||||
ecdsa_recover: cost!(seal_ecdsa_recover),
|
||||
sr25519_verify: cost!(seal_sr25519_verify),
|
||||
sr25519_verify_per_byte: cost!(seal_sr25519_verify_per_byte),
|
||||
ecdsa_to_eth_address: cost!(seal_ecdsa_to_eth_address),
|
||||
reentrance_count: cost!(seal_reentrance_count),
|
||||
account_reentrance_count: cost!(seal_account_reentrance_count),
|
||||
|
||||
@@ -2900,6 +2900,72 @@ fn ecdsa_recover() {
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sr25519_verify() {
|
||||
let (wasm, _code_hash) = compile_module::<Test>("sr25519_verify").unwrap();
|
||||
|
||||
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
|
||||
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
|
||||
|
||||
// Instantiate the sr25519_verify contract.
|
||||
let addr = Contracts::bare_instantiate(
|
||||
ALICE,
|
||||
100_000,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
Code::Upload(wasm),
|
||||
vec![],
|
||||
vec![],
|
||||
false,
|
||||
)
|
||||
.result
|
||||
.unwrap()
|
||||
.account_id;
|
||||
|
||||
let call_with = |message: &[u8; 11]| {
|
||||
// Alice's signature for "hello world"
|
||||
#[rustfmt::skip]
|
||||
let signature: [u8; 64] = [
|
||||
184, 49, 74, 238, 78, 165, 102, 252, 22, 92, 156, 176, 124, 118, 168, 116, 247,
|
||||
99, 0, 94, 2, 45, 9, 170, 73, 222, 182, 74, 60, 32, 75, 64, 98, 174, 69, 55, 83,
|
||||
85, 180, 98, 208, 75, 231, 57, 205, 62, 4, 105, 26, 136, 172, 17, 123, 99, 90, 255,
|
||||
228, 54, 115, 63, 30, 207, 205, 131,
|
||||
];
|
||||
|
||||
// Alice's public key
|
||||
#[rustfmt::skip]
|
||||
let public_key: [u8; 32] = [
|
||||
212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44,
|
||||
133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125,
|
||||
];
|
||||
|
||||
let mut params = vec![];
|
||||
params.extend_from_slice(&signature);
|
||||
params.extend_from_slice(&public_key);
|
||||
params.extend_from_slice(message);
|
||||
|
||||
<Pallet<Test>>::bare_call(
|
||||
ALICE,
|
||||
addr.clone(),
|
||||
0,
|
||||
GAS_LIMIT,
|
||||
None,
|
||||
params,
|
||||
false,
|
||||
Determinism::Enforced,
|
||||
)
|
||||
.result
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
// verification should succeed for "hello world"
|
||||
assert_return_code!(call_with(&b"hello world"), RuntimeReturnCode::Success);
|
||||
|
||||
// verification should fail for other messages
|
||||
assert_return_code!(call_with(&b"hello worlD"), RuntimeReturnCode::Sr25519VerifyFailed);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upload_code_works() {
|
||||
let (wasm, code_hash) = compile_module::<Test>("dummy").unwrap();
|
||||
|
||||
@@ -434,6 +434,7 @@ mod tests {
|
||||
gas_meter: GasMeter<Test>,
|
||||
debug_buffer: Vec<u8>,
|
||||
ecdsa_recover: RefCell<Vec<([u8; 65], [u8; 32])>>,
|
||||
sr25519_verify: RefCell<Vec<([u8; 64], Vec<u8>, [u8; 32])>>,
|
||||
code_hashes: Vec<CodeHash<Test>>,
|
||||
}
|
||||
|
||||
@@ -458,6 +459,7 @@ mod tests {
|
||||
gas_meter: GasMeter::new(Weight::from_parts(10_000_000_000, 10 * 1024 * 1024)),
|
||||
debug_buffer: Default::default(),
|
||||
ecdsa_recover: Default::default(),
|
||||
sr25519_verify: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -612,6 +614,10 @@ mod tests {
|
||||
self.ecdsa_recover.borrow_mut().push((*signature, *message_hash));
|
||||
Ok([3; 33])
|
||||
}
|
||||
fn sr25519_verify(&self, signature: &[u8; 64], message: &[u8], pub_key: &[u8; 32]) -> bool {
|
||||
self.sr25519_verify.borrow_mut().push((*signature, message.to_vec(), *pub_key));
|
||||
true
|
||||
}
|
||||
fn contract_info(&mut self) -> &mut crate::ContractInfo<Self::T> {
|
||||
unimplemented!()
|
||||
}
|
||||
@@ -1319,6 +1325,49 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contract_sr25519() {
|
||||
const CODE_SR25519: &str = r#"
|
||||
(module
|
||||
(import "seal0" "sr25519_verify" (func $sr25519_verify (param i32 i32 i32 i32) (result i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
(func (export "call")
|
||||
(drop
|
||||
(call $sr25519_verify
|
||||
(i32.const 0) ;; Pointer to signature.
|
||||
(i32.const 64) ;; Pointer to public key.
|
||||
(i32.const 16) ;; message length.
|
||||
(i32.const 96) ;; Pointer to message.
|
||||
)
|
||||
)
|
||||
)
|
||||
(func (export "deploy"))
|
||||
|
||||
;; Signature (64 bytes)
|
||||
(data (i32.const 0)
|
||||
"\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01"
|
||||
"\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01"
|
||||
"\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01"
|
||||
"\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01"
|
||||
)
|
||||
|
||||
;; public key (32 bytes)
|
||||
(data (i32.const 64)
|
||||
"\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01"
|
||||
"\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01"
|
||||
)
|
||||
|
||||
;; message. (16 bytes)
|
||||
(data (i32.const 96)
|
||||
"\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01"
|
||||
)
|
||||
)
|
||||
"#;
|
||||
let mut mock_ext = MockExt::default();
|
||||
assert_ok!(execute(&CODE_SR25519, vec![], &mut mock_ext));
|
||||
assert_eq!(mock_ext.sr25519_verify.into_inner(), [([1; 64], [1; 16].to_vec(), [1; 32])]);
|
||||
}
|
||||
|
||||
const CODE_GET_STORAGE: &str = r#"
|
||||
(module
|
||||
(import "seal0" "seal_get_storage" (func $seal_get_storage (param i32 i32 i32) (result i32)))
|
||||
|
||||
@@ -109,6 +109,8 @@ pub enum ReturnCode {
|
||||
/// ECDSA compressed pubkey conversion into Ethereum address failed (most probably
|
||||
/// wrong pubkey provided).
|
||||
EcdsaRecoverFailed = 11,
|
||||
/// sr25519 signature verification failed.
|
||||
Sr25519VerifyFailed = 12,
|
||||
}
|
||||
|
||||
impl From<ExecReturnValue> for ReturnCode {
|
||||
@@ -251,6 +253,8 @@ pub enum RuntimeCosts {
|
||||
HashBlake128(u32),
|
||||
/// Weight of calling `seal_ecdsa_recover`.
|
||||
EcdsaRecovery,
|
||||
/// Weight of calling `seal_sr25519_verify` for the given input size.
|
||||
Sr25519Verify(u32),
|
||||
/// Weight charged by a chain extension through `seal_call_chain_extension`.
|
||||
ChainExtension(Weight),
|
||||
/// Weight charged for calling into the runtime.
|
||||
@@ -336,6 +340,9 @@ impl RuntimeCosts {
|
||||
.hash_blake2_128
|
||||
.saturating_add(s.hash_blake2_128_per_byte.saturating_mul(len.into())),
|
||||
EcdsaRecovery => s.ecdsa_recover,
|
||||
Sr25519Verify(len) => s
|
||||
.sr25519_verify
|
||||
.saturating_add(s.sr25519_verify_per_byte.saturating_mul(len.into())),
|
||||
ChainExtension(weight) => weight,
|
||||
CallRuntime(weight) => weight,
|
||||
SetCodeHash => s.set_code_hash,
|
||||
@@ -2466,6 +2473,46 @@ pub mod env {
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify a sr25519 signature
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// - `signature_ptr`: the pointer into the linear memory where the signature is placed. Should
|
||||
/// be a value of 64 bytes.
|
||||
/// - `pub_key_ptr`: the pointer into the linear memory where the public key is placed. Should
|
||||
/// be a value of 32 bytes.
|
||||
/// - `message_len`: the length of the message payload.
|
||||
/// - `message_ptr`: the pointer into the linear memory where the message is placed.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// - `ReturnCode::Sr25519VerifyFailed
|
||||
#[unstable]
|
||||
fn sr25519_verify(
|
||||
ctx: _,
|
||||
memory: _,
|
||||
signature_ptr: u32,
|
||||
pub_key_ptr: u32,
|
||||
message_len: u32,
|
||||
message_ptr: u32,
|
||||
) -> Result<ReturnCode, TrapReason> {
|
||||
ctx.charge_gas(RuntimeCosts::Sr25519Verify(message_len))?;
|
||||
|
||||
let mut signature: [u8; 64] = [0; 64];
|
||||
ctx.read_sandbox_memory_into_buf(memory, signature_ptr, &mut signature)?;
|
||||
|
||||
let mut pub_key: [u8; 32] = [0; 32];
|
||||
ctx.read_sandbox_memory_into_buf(memory, pub_key_ptr, &mut pub_key)?;
|
||||
|
||||
let message: Vec<u8> = ctx.read_sandbox_memory(memory, message_ptr, message_len)?;
|
||||
|
||||
if ctx.ext.sr25519_verify(&signature, &message, &pub_key) {
|
||||
Ok(ReturnCode::Success)
|
||||
} else {
|
||||
Ok(ReturnCode::Sr25519VerifyFailed)
|
||||
}
|
||||
}
|
||||
|
||||
/// Replace the contract code at the specified address with new code.
|
||||
///
|
||||
/// # Note
|
||||
|
||||
Generated
+985
-891
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user