Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 81b423b

Browse files
authoredJan 16, 2025··
feat: add receipt action for deploying global contract code (#12737)
This PR includes mostly wiring, actual implementation will be added separately to make it easier to review. I explicitly don't want to introduce compile time feature for this as it makes code much harder to work with. `validate_action` makes sure that newly added action cannot be used before stabilisation. Part of #12715.
1 parent 8806e63 commit 81b423b

File tree

11 files changed

+201
-46
lines changed

11 files changed

+201
-46
lines changed
 

‎chain/rosetta-rpc/src/adapters/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,9 @@ impl From<NearActions> for Vec<crate::models::Operation> {
500500

501501
operations.extend(delegated_operations);
502502
} // TODO(#8469): Implement delegate action support, for now they are ignored.
503+
near_primitives::transaction::Action::DeployGlobalContract(_action) => {
504+
// TODO(#12639): Implement global contract deploys support, ignored for now.
505+
}
503506
}
504507
}
505508
operations

‎core/primitives-core/src/version.rs

+2
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ pub enum ProtocolFeature {
196196
ExcludeExistingCodeFromWitnessForCodeLen,
197197
/// Use the block height instead of the block hash to calculate the receipt ID.
198198
BlockHeightForReceiptId,
199+
GlobalContracts,
199200
}
200201

201202
impl ProtocolFeature {
@@ -286,6 +287,7 @@ impl ProtocolFeature {
286287
ProtocolFeature::ExcludeExistingCodeFromWitnessForCodeLen => 148,
287288
ProtocolFeature::BlockHeightForReceiptId => 149,
288289
// Place features that are not yet in Nightly below this line.
290+
ProtocolFeature::GlobalContracts => 200,
289291
}
290292
}
291293

‎core/primitives/src/action/mod.rs

+60
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,59 @@ impl fmt::Debug for DeployContractAction {
106106
}
107107
}
108108

109+
#[serde_as]
110+
#[derive(
111+
BorshSerialize,
112+
BorshDeserialize,
113+
serde::Serialize,
114+
serde::Deserialize,
115+
PartialEq,
116+
Eq,
117+
Clone,
118+
ProtocolSchema,
119+
Debug,
120+
)]
121+
#[repr(u8)]
122+
pub enum GlobalContractDeployMode {
123+
/// Contract is deployed under its code hash.
124+
/// Users will be able reference it by that hash.
125+
/// This effectively makes the contract immutable.
126+
CodeHash,
127+
/// Contract is deployed under the onwer account id.
128+
/// Users will be able reference it by that account id.
129+
/// This allows the owner to update the contract for all its users.
130+
AccountId,
131+
}
132+
133+
/// Deploy global contract action
134+
#[serde_as]
135+
#[derive(
136+
BorshSerialize,
137+
BorshDeserialize,
138+
serde::Serialize,
139+
serde::Deserialize,
140+
PartialEq,
141+
Eq,
142+
Clone,
143+
ProtocolSchema,
144+
)]
145+
pub struct DeployGlobalContractAction {
146+
/// WebAssembly binary
147+
#[serde_as(as = "Base64")]
148+
pub code: Vec<u8>,
149+
150+
pub deploy_mode: GlobalContractDeployMode,
151+
}
152+
153+
impl fmt::Debug for DeployGlobalContractAction {
154+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155+
f.debug_struct("DeployGlobalContractAction")
156+
.field("code", &format_args!("{}", base64(&self.code)))
157+
.field("deploy_mode", &format_args!("{:?}", &self.deploy_mode))
158+
.finish()
159+
}
160+
}
161+
109162
#[serde_as]
110163
#[derive(
111164
BorshSerialize,
@@ -216,6 +269,7 @@ pub enum Action {
216269
DeleteKey(Box<DeleteKeyAction>),
217270
DeleteAccount(DeleteAccountAction),
218271
Delegate(Box<delegate::SignedDelegateAction>),
272+
DeployGlobalContract(DeployGlobalContractAction),
219273
#[cfg(feature = "protocol_feature_nonrefundable_transfer_nep491")]
220274
/// Makes a non-refundable transfer for storage allowance.
221275
/// Only possible during new account creation.
@@ -261,6 +315,12 @@ impl From<DeployContractAction> for Action {
261315
}
262316
}
263317

318+
impl From<DeployGlobalContractAction> for Action {
319+
fn from(deploy_global_contract_action: DeployGlobalContractAction) -> Self {
320+
Self::DeployGlobalContract(deploy_global_contract_action)
321+
}
322+
}
323+
264324
impl From<FunctionCallAction> for Action {
265325
fn from(function_call_action: FunctionCallAction) -> Self {
266326
Self::FunctionCall(Box::new(function_call_action))

‎core/primitives/src/views.rs

+30
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
//! from the source structure in the relevant `From<SourceStruct>` impl.
66
use crate::account::{AccessKey, AccessKeyPermission, Account, FunctionCallPermission};
77
use crate::action::delegate::{DelegateAction, SignedDelegateAction};
8+
use crate::action::{DeployGlobalContractAction, GlobalContractDeployMode};
89
use crate::bandwidth_scheduler::BandwidthRequests;
910
use crate::block::{Block, BlockHeader, Tip};
1011
use crate::block_header::BlockHeaderInnerLite;
@@ -1170,6 +1171,14 @@ pub enum ActionView {
11701171
delegate_action: DelegateAction,
11711172
signature: Signature,
11721173
},
1174+
DeployGlobalContract {
1175+
#[serde_as(as = "Base64")]
1176+
code: Vec<u8>,
1177+
},
1178+
DeployGlobalContractByAccountId {
1179+
#[serde_as(as = "Base64")]
1180+
code: Vec<u8>,
1181+
},
11731182
}
11741183

11751184
impl From<Action> for ActionView {
@@ -1206,6 +1215,15 @@ impl From<Action> for ActionView {
12061215
delegate_action: action.delegate_action,
12071216
signature: action.signature,
12081217
},
1218+
Action::DeployGlobalContract(action) => {
1219+
let code = hash(&action.code).as_ref().to_vec();
1220+
match action.deploy_mode {
1221+
GlobalContractDeployMode::CodeHash => ActionView::DeployGlobalContract { code },
1222+
GlobalContractDeployMode::AccountId => {
1223+
ActionView::DeployGlobalContractByAccountId { code }
1224+
}
1225+
}
1226+
}
12091227
}
12101228
}
12111229
}
@@ -1247,6 +1265,18 @@ impl TryFrom<ActionView> for Action {
12471265
ActionView::Delegate { delegate_action, signature } => {
12481266
Action::Delegate(Box::new(SignedDelegateAction { delegate_action, signature }))
12491267
}
1268+
ActionView::DeployGlobalContract { code } => {
1269+
Action::DeployGlobalContract(DeployGlobalContractAction {
1270+
code,
1271+
deploy_mode: GlobalContractDeployMode::CodeHash,
1272+
})
1273+
}
1274+
ActionView::DeployGlobalContractByAccountId { code } => {
1275+
Action::DeployGlobalContract(DeployGlobalContractAction {
1276+
code,
1277+
deploy_mode: GlobalContractDeployMode::AccountId,
1278+
})
1279+
}
12501280
})
12511281
}
12521282
}

‎runtime/runtime/src/actions.rs

+18-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use near_crypto::PublicKey;
99
use near_parameters::{AccountCreationConfig, ActionCosts, RuntimeConfig, RuntimeFeesConfig};
1010
use near_primitives::account::{AccessKey, AccessKeyPermission, Account};
1111
use near_primitives::action::delegate::{DelegateAction, SignedDelegateAction};
12+
use near_primitives::action::DeployGlobalContractAction;
1213
use near_primitives::checked_feature;
1314
use near_primitives::config::ViewConfig;
1415
use near_primitives::errors::{ActionError, ActionErrorKind, InvalidAccessKeyError, RuntimeError};
@@ -653,6 +654,16 @@ pub(crate) fn action_deploy_contract(
653654
Ok(())
654655
}
655656

657+
pub(crate) fn action_deploy_global_contract(
658+
_account_id: &AccountId,
659+
_deploy_contract: &DeployGlobalContractAction,
660+
_result: &mut ActionResult,
661+
) -> Result<(), StorageError> {
662+
let _span = tracing::debug_span!(target: "runtime", "action_deploy_global_contract").entered();
663+
// TODO(#12715): implement global contract distribution
664+
Ok(())
665+
}
666+
656667
pub(crate) fn action_delete_account(
657668
state_update: &mut TrieUpdate,
658669
account: &mut Option<Account>,
@@ -1025,7 +1036,11 @@ pub(crate) fn check_actor_permissions(
10251036
account_id: &AccountId,
10261037
) -> Result<(), ActionError> {
10271038
match action {
1028-
Action::DeployContract(_) | Action::Stake(_) | Action::AddKey(_) | Action::DeleteKey(_) => {
1039+
Action::DeployContract(_)
1040+
| Action::Stake(_)
1041+
| Action::AddKey(_)
1042+
| Action::DeleteKey(_)
1043+
| Action::DeployGlobalContract(_) => {
10291044
if actor_id != account_id {
10301045
return Err(ActionErrorKind::ActorNoPermission {
10311046
account_id: account_id.clone(),
@@ -1137,7 +1152,8 @@ pub(crate) fn check_account_existence(
11371152
| Action::AddKey(_)
11381153
| Action::DeleteKey(_)
11391154
| Action::DeleteAccount(_)
1140-
| Action::Delegate(_) => {
1155+
| Action::Delegate(_)
1156+
| Action::DeployGlobalContract(_) => {
11411157
if account.is_none() {
11421158
return Err(ActionErrorKind::AccountDoesNotExist {
11431159
account_id: account_id.clone(),

‎runtime/runtime/src/config.rs

+8
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,10 @@ pub fn total_send_fees(
150150
&delegate_action.receiver_id,
151151
)?
152152
}
153+
DeployGlobalContract(_deploy_global_contract_action) => {
154+
// TODO(#12717): implement send fees for global contract deploy
155+
1
156+
}
153157
};
154158
result = safe_add_gas(result, delta)?;
155159
}
@@ -241,6 +245,10 @@ pub fn exec_fee(config: &RuntimeConfig, action: &Action, receiver_id: &AccountId
241245
DeleteKey(_) => fees.fee(ActionCosts::delete_key).exec_fee(),
242246
DeleteAccount(_) => fees.fee(ActionCosts::delete_account).exec_fee(),
243247
Delegate(_) => fees.fee(ActionCosts::delegate).exec_fee(),
248+
DeployGlobalContract(_deploy_global_contract_action) => {
249+
// TODO(#12717): implement exec fees for global contract deploys
250+
1
251+
}
244252
}
245253
}
246254

‎runtime/runtime/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,9 @@ impl Runtime {
484484
apply_state.current_protocol_version,
485485
)?;
486486
}
487+
Action::DeployGlobalContract(deploy_global_contract) => {
488+
action_deploy_global_contract(account_id, deploy_global_contract, &mut result)?;
489+
}
487490
Action::FunctionCall(function_call) => {
488491
let account = account.as_mut().expect(EXPECT_ACCOUNT_EXISTS);
489492
let contract = preparation_pipeline.get_contract(

‎runtime/runtime/src/pipelining.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,8 @@ impl ReceiptPreparationPipeline {
189189
| Action::Stake(_)
190190
| Action::AddKey(_)
191191
| Action::DeleteKey(_)
192-
| Action::DeleteAccount(_) => {}
192+
| Action::DeleteAccount(_)
193+
| Action::DeployGlobalContract(_) => {}
193194
#[cfg(feature = "protocol_feature_nonrefundable_transfer_nep491")]
194195
Action::NonrefundableStorageTransfer(_) => {}
195196
}

‎runtime/runtime/src/verifier.rs

+26
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use near_crypto::key_conversion::is_valid_staking_key;
55
use near_parameters::RuntimeConfig;
66
use near_primitives::account::AccessKeyPermission;
77
use near_primitives::action::delegate::SignedDelegateAction;
8+
use near_primitives::action::DeployGlobalContractAction;
89
use near_primitives::checked_feature;
910
use near_primitives::errors::{
1011
ActionsValidationError, InvalidAccessKeyError, InvalidTxError, ReceiptValidationError,
@@ -433,6 +434,9 @@ pub fn validate_action(
433434
match action {
434435
Action::CreateAccount(_) => Ok(()),
435436
Action::DeployContract(a) => validate_deploy_contract_action(limit_config, a),
437+
Action::DeployGlobalContract(a) => {
438+
validate_deploy_global_contract_action(limit_config, a, current_protocol_version)
439+
}
436440
Action::FunctionCall(a) => validate_function_call_action(limit_config, a),
437441
Action::Transfer(_) => Ok(()),
438442
#[cfg(feature = "protocol_feature_nonrefundable_transfer_nep491")]
@@ -472,6 +476,28 @@ fn validate_deploy_contract_action(
472476
Ok(())
473477
}
474478

479+
/// Validates `DeployGlobalContractAction`. Checks that the given contract size doesn't exceed the limit.
480+
fn validate_deploy_global_contract_action(
481+
limit_config: &LimitConfig,
482+
action: &DeployGlobalContractAction,
483+
current_protocol_version: ProtocolVersion,
484+
) -> Result<(), ActionsValidationError> {
485+
if !checked_feature!("stable", GlobalContracts, current_protocol_version) {
486+
return Err(ActionsValidationError::UnsupportedProtocolFeature {
487+
protocol_feature: "GlobalContracts".to_owned(),
488+
version: current_protocol_version,
489+
});
490+
}
491+
if action.code.len() as u64 > limit_config.max_contract_size {
492+
return Err(ActionsValidationError::ContractSizeExceeded {
493+
size: action.code.len() as u64,
494+
limit: limit_config.max_contract_size,
495+
});
496+
}
497+
498+
Ok(())
499+
}
500+
475501
/// Validates `FunctionCallAction`. Checks that the method name length doesn't exceed the limit and
476502
/// the length of the arguments doesn't exceed the limit.
477503
fn validate_function_call_action(

0 commit comments

Comments
 (0)
Please sign in to comment.