Skip to content

Commit 7210c1c

Browse files
Merge pull request #41 from Peersyst/contracts/feat/updatable
[TA-5570] feat(contracts): make contracts updatable
2 parents 3cc4f08 + b006168 commit 7210c1c

File tree

4 files changed

+334
-3
lines changed

4 files changed

+334
-3
lines changed

contracts/auth0-guard/src/lib.rs

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Find all our documentation at https://docs.near.org
2-
use near_sdk::{near, AccountId, env};
2+
use near_sdk::{near, AccountId, env, Promise, NearToken, Gas};
33
use near_sdk::serde_json;
44
use serde::{Deserialize, Serialize};
55
use crypto_bigint::{BoxedUint, Odd};
@@ -11,6 +11,7 @@ pub mod rsa;
1111
pub mod jwt;
1212

1313
const MAX_JWT_SIZE: u128 = 7168;
14+
const MIGRATION_TGAS: u64 = 10;
1415
const PRECISION: u32 = 2048;
1516

1617
/// Custom claims structure for FastAuth JWT tokens
@@ -80,6 +81,51 @@ impl Auth0Guard {
8081
}
8182
}
8283

84+
/// Updates the contract
85+
/// # Panics
86+
/// * If the caller is not the owner
87+
/// # Returns
88+
/// * Promise resolving to the contract update result
89+
pub fn update_contract(&self) -> Promise {
90+
self.only_owner();
91+
let code = env::input().expect("Error: No input").to_vec();
92+
93+
// Deploy the contract on self
94+
Promise::new(env::current_account_id())
95+
.deploy_contract(code)
96+
// When the contract update requires a state migration, you need to make a function call to
97+
// the `migrate` function, to handle all the state migrations
98+
.function_call(
99+
"migrate".to_string(),
100+
vec![],
101+
NearToken::from_near(0),
102+
Gas::from_tgas(MIGRATION_TGAS),
103+
)
104+
.as_return()
105+
}
106+
107+
/// Migrates the contract state
108+
/// # Returns
109+
/// * The migrated contract state
110+
#[private]
111+
#[init(ignore_state)]
112+
pub fn migrate() -> Self {
113+
env::log_str("migrate");
114+
if env::state_exists() {
115+
env::log_str("state exists: migrating state");
116+
let prev_state = env::state_read::<Self>().expect("Error: No previous state");
117+
Self {
118+
owner: prev_state.owner,
119+
issuer: prev_state.issuer,
120+
n_component: prev_state.n_component,
121+
e_component: prev_state.e_component,
122+
}
123+
} else {
124+
env::log_str("state does not exist: initializing default state");
125+
Self::default()
126+
}
127+
}
128+
83129
/// Checks if the caller is the contract owner
84130
///
85131
/// This is an internal method used to restrict access to administrative functions
@@ -308,4 +354,55 @@ mod tests {
308354
let result = contract.verify("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Imd2bXRWLXVzMk83N21tam5NR3FCMCJ9.eyJmYXR4biI6WzE4LDAsMCwwLDEwMiw5Nyw0NSwxMDMsMTE3LDEwNSwxMDgsMTA4LDEwMSwxMDksNDYsMTE2LDEwMSwxMTUsMTE2LDExMCwxMDEsMTE2LDEsMzksMTIwLDIsNTAsNDIsMjQ3LDI0MywyMjMsMTUyLDk3LDI1MSwyOCwxNTMsMzgsMTU0LDEzMiwxODQsMTIzLDE1MiwxNTAsMjQ3LDIxNiw4Nyw1Myw3Niw0MiwxMjcsMTksMTI4LDgsMTgyLDIwOSwyNTEsMjcsMTgwLDIwMzcsMTg1LDI0NywzNSw2LDcxLDMxLDk2LDExMCw2NiwxMjEsMTA1LDIyOCwyNSwyNTAyMDYsMTgzLDE5MSwzNiwxMDksNzUsMTA1LDk3LDI5LDQwLDE0Miw4LDI0NCw5Miw0MSwxODYsMTI2LDg2LDExMSwwLDAsMjAsMCwwLDAsOTgsMTEyLDExNSwxMDUsMTE1LDExNiwxMDQsMTAxLDExMCwxMDEsOTcsMTE0LDQ2LDExNiwxMDEsMTE1LDExNiwxMTAxMDEsMTE2LDUyLDIxLDgzLDc1LDIyMCwxNzAsMTA0LDE3OSwxMzYsMjQ0LDE2OCwxMTgsMjUsOTIsMjI0LDY4LDEzMSwxNTIsMTUyLDQxLDI0NSwxOTMsMjI5LDE4Miw4LDEzNiw4NiwyMzcsMTQxLDIxNywxNTcsMTU1LDEsMCwwLDAsMywxMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMF0sImlzcyI6Imh0dHBzOi8vZGV2LWdiMWg1eXJlcGI4NWpzdHoudXMuYXV0aDAuY29tLyIsInN1YiI6Imdvb2dsZS1vYXV0aDJ8MTA1NDQ2OTI1MjM1NjMyNzc3Mzk3IiwiYXVkIjpbImh0dHBzOi8vZmFzdC1hdXRoLXBvYy5jb20iLCJodHRwczovL2Rldi1nYjFoNXlyZXBiODVqc3R6LnVzLmF1dGgwLmNvbS91c2VyaW5mbyJdLCJpYXQiOjE3NDY2OTczODYsImV4cCI6MTc0Njc4Mzc4Niwic2NvcGUiOiJvcGVuaWQgdHJhbnNhY3Rpb246c2VuZC10cmFuc2FjdGlvbiIsImF6cCI6IjdEbWhXdXVnVVZKRE5TSjRlZE5PVEZtMGM5OHhzOWhwIn0.XChULVjx06hAGdBND54qFWr9KVdP95GXLc4Y8KzC9Fpj4Ky6E76ijbjE9ATVpSylKKMHrpVxjQHMoszyPbkHA759mf9x3gr5mOEkUy2WR8N35SYTZkbB77l8pA5o_zxOS9SKewBrGyZWpij0OyiM-Eqom3nwer3Aw3UPFyVB2ucpQkW-eJVrlNpKB80xhr1lCRBiHvPEnNH2Mk5Ok3x-uRzPTRq__hMjuY3F_udF4cEbeJGoWA2QGr1gTeUMKJyGvSThEk2xxq5xagXDA6FPq5DHi1Q9GxUlA3pPeb7zhNseUoGm1AdCTlqqGwgakUkuWj7I5miBjNu6qd-fQfkGXQ".to_string(), vec![18,0,0,0,102,97,45,103,117,105,108,108,101,109,46,116,101,115,116,110,101,116,1,39,120,2,50,42,247,243,223,152,97,251,28,153,38,154,132,184,123,152,150,247,216,87,53,76,42,127,19,128,8,182,209,251,27,180,20,37,185,247,35,6,71,31,96,110,66,121,105,228,25,250,206,183,191,36,109,75,105,97,29,40,142,8,244,92,41,186,126,86,111,0,0,20,0,0,0,98,111,115,105,115,116,104,101,110,101,97,114,46,116,101,115,116,110,101,116,52,21,83,75,220,170,104,179,136,244,168,118,25,92,224,68,131,152,152,41,245,193,229,182,8,136,86,237,141,217,157,155,1,0,0,0,3,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]);
309355
assert_eq!(result, (false, "".to_string()));
310356
}
357+
358+
#[test]
359+
fn test_update_contract_owner_success() {
360+
use near_sdk::test_utils::{accounts, VMContextBuilder};
361+
use near_sdk::testing_env;
362+
363+
let owner = accounts(1);
364+
let mut context = VMContextBuilder::new();
365+
context
366+
.current_account_id(accounts(0))
367+
.signer_account_id(owner.clone())
368+
.predecessor_account_id(owner.clone());
369+
testing_env!(context.build());
370+
371+
let contract = Auth0Guard {
372+
n_component: vec![1, 2, 3],
373+
e_component: vec![1, 0, 1],
374+
owner: owner.clone(),
375+
issuer: "https://test.auth0.com/".to_string(),
376+
};
377+
378+
// This should not panic for owner (input handling is tested in integration tests)
379+
let _promise = contract.update_contract();
380+
}
381+
382+
#[test]
383+
#[should_panic(expected = "Only the owner can call this function")]
384+
fn test_update_contract_non_owner_fails() {
385+
use near_sdk::test_utils::{accounts, VMContextBuilder};
386+
use near_sdk::testing_env;
387+
388+
let owner = accounts(1);
389+
let non_owner = accounts(2);
390+
let mut context = VMContextBuilder::new();
391+
context
392+
.current_account_id(accounts(0))
393+
.signer_account_id(non_owner.clone())
394+
.predecessor_account_id(non_owner.clone());
395+
testing_env!(context.build());
396+
397+
let contract = Auth0Guard {
398+
n_component: vec![1, 2, 3],
399+
e_component: vec![1, 0, 1],
400+
owner: owner.clone(),
401+
issuer: "https://test.auth0.com/".to_string(),
402+
};
403+
404+
// This should panic because non-owner is calling
405+
contract.update_contract();
406+
}
407+
311408
}

contracts/fa/src/lib.rs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ use crate::external_contracts::{external_guard, mpc_contract, mpc_contract_legac
99
const DEFAULT_MPC_KEY_VERSION: u32 = 0;
1010
const DEFAULT_DOMAIN_ID: u64 = 0;
1111

12+
const MIGRATION_TGAS: u64 = 10;
13+
1214
const CONTRACT_VERSION: &str = "0.1.0";
1315

1416
/// Supported signature algorithms
@@ -205,6 +207,55 @@ impl FastAuth {
205207
self.mpc_key_version
206208
}
207209

210+
/// Updates the contract
211+
/// # Panics
212+
/// * If the caller is not the owner
213+
/// # Returns
214+
/// * Promise resolving to the contract update result
215+
pub fn update_contract(&self) -> Promise {
216+
self.only_owner();
217+
let code = env::input().expect("Error: No input").to_vec();
218+
219+
// Deploy the contract on self
220+
Promise::new(env::current_account_id())
221+
.deploy_contract(code)
222+
// When the contract update requires a state migration, you need to make a function call to
223+
// the `migrate` function, to handle all the state migrations
224+
.function_call(
225+
"migrate".to_string(),
226+
vec![],
227+
NearToken::from_near(0),
228+
Gas::from_tgas(MIGRATION_TGAS),
229+
)
230+
.as_return()
231+
}
232+
233+
/// Migrates the contract state
234+
/// # Returns
235+
/// * The migrated contract state
236+
#[private]
237+
#[init(ignore_state)]
238+
pub fn migrate() -> Self {
239+
env::log_str("migrate");
240+
if env::state_exists() {
241+
env::log_str("state exists: migrating state");
242+
let prev_state = env::state_read::<Self>().expect("Error: No previous state");
243+
Self {
244+
guards: prev_state.guards,
245+
owner: prev_state.owner,
246+
mpc_address: prev_state.mpc_address,
247+
mpc_domain_id: prev_state.mpc_domain_id,
248+
mpc_key_version: prev_state.mpc_key_version,
249+
version: prev_state.version,
250+
pauser: prev_state.pauser,
251+
paused: prev_state.paused,
252+
}
253+
} else {
254+
env::log_str("state does not exist: initializing default state");
255+
Self::default()
256+
}
257+
}
258+
208259
/// Sets the MPC domain ID
209260
/// # Arguments
210261
/// * `mpc_domain_id` - The new MPC domain ID number
@@ -642,6 +693,64 @@ mod tests {
642693
assert_eq!(contract.version(), version);
643694
}
644695

696+
#[test]
697+
fn update_contract_owner_success() {
698+
use near_sdk::test_utils::{accounts, VMContextBuilder};
699+
use near_sdk::testing_env;
700+
701+
let owner = accounts(1);
702+
let mut context = VMContextBuilder::new();
703+
context
704+
.current_account_id(accounts(0))
705+
.signer_account_id(owner.clone())
706+
.predecessor_account_id(owner.clone());
707+
testing_env!(context.build());
708+
709+
let contract = FastAuth {
710+
guards: HashMap::new(),
711+
owner: owner.clone(),
712+
mpc_address: env::current_account_id(),
713+
mpc_domain_id: DEFAULT_DOMAIN_ID,
714+
mpc_key_version: DEFAULT_MPC_KEY_VERSION,
715+
version: CONTRACT_VERSION.to_string(),
716+
paused: false,
717+
pauser: env::current_account_id()
718+
};
719+
720+
// This should not panic for owner (input handling is tested in integration tests)
721+
let _promise = contract.update_contract();
722+
}
723+
724+
#[test]
725+
#[should_panic(expected = "Only the owner can call this function")]
726+
fn update_contract_non_owner_fails() {
727+
use near_sdk::test_utils::{accounts, VMContextBuilder};
728+
use near_sdk::testing_env;
729+
730+
let owner = accounts(1);
731+
let non_owner = accounts(2);
732+
let mut context = VMContextBuilder::new();
733+
context
734+
.current_account_id(accounts(0))
735+
.signer_account_id(non_owner.clone())
736+
.predecessor_account_id(non_owner.clone());
737+
testing_env!(context.build());
738+
739+
let contract = FastAuth {
740+
guards: HashMap::new(),
741+
owner: owner.clone(),
742+
mpc_address: env::current_account_id(),
743+
mpc_domain_id: DEFAULT_DOMAIN_ID,
744+
mpc_key_version: DEFAULT_MPC_KEY_VERSION,
745+
version: CONTRACT_VERSION.to_string(),
746+
paused: false,
747+
pauser: env::current_account_id()
748+
};
749+
750+
// This should panic because non-owner is calling
751+
contract.update_contract();
752+
}
753+
645754
#[test]
646755
fn bytes_to_hex() {
647756
let contract = FastAuth { guards: HashMap::new(), owner: env::current_account_id(), mpc_address: env::current_account_id(), mpc_key_version: DEFAULT_MPC_KEY_VERSION, mpc_domain_id: DEFAULT_DOMAIN_ID, version: CONTRACT_VERSION.to_string(), paused: false, pauser: env::current_account_id() };

contracts/jwt-guard-router/src/lib.rs

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Find all our documentation at https://docs.near.org
2-
use near_sdk::{near, AccountId, env, NearToken, Promise, PromiseError};
2+
use near_sdk::{near, AccountId, env, NearToken, Promise, PromiseError, Gas };
33
use near_sdk::store::LookupMap;
44
use crate::external_contract::jwt_guard;
55

@@ -10,6 +10,8 @@ pub const MAX_ACCOUNT_BYTES_LENGTH: u128 = 64;
1010
// NOTE: 1 NEAR
1111
pub const CONTINGENCY_DEPOSIT: u128 = 1_000_000_000_000_000_000_000_000;
1212

13+
const MIGRATION_TGAS: u64 = 10;
14+
1315
const MAP_KEY: &[u8] = b"g";
1416

1517
/// Contract that manages JWT guard accounts
@@ -52,6 +54,49 @@ impl JwtGuardRouter {
5254
}
5355
}
5456

57+
/// Updates the contract
58+
/// # Panics
59+
/// * If the caller is not the owner
60+
/// # Returns
61+
/// * Promise resolving to the contract update result
62+
pub fn update_contract(&self) -> Promise {
63+
self.only_owner();
64+
let code = env::input().expect("Error: No input").to_vec();
65+
66+
// Deploy the contract on self
67+
Promise::new(env::current_account_id())
68+
.deploy_contract(code)
69+
// When the contract update requires a state migration, you need to make a function call to
70+
// the `migrate` function, to handle all the state migrations
71+
.function_call(
72+
"migrate".to_string(),
73+
vec![],
74+
NearToken::from_near(0),
75+
Gas::from_tgas(MIGRATION_TGAS),
76+
)
77+
.as_return()
78+
}
79+
80+
/// Migrates the contract state
81+
/// # Returns
82+
/// * The migrated contract state
83+
#[private]
84+
#[init(ignore_state)]
85+
pub fn migrate() -> Self {
86+
env::log_str("migrate");
87+
if env::state_exists() {
88+
env::log_str("state exists: migrating state");
89+
let prev_state = env::state_read::<Self>().expect("Error: No previous state");
90+
Self {
91+
guards: prev_state.guards,
92+
owner: prev_state.owner,
93+
}
94+
} else {
95+
env::log_str("state does not exist: initializing default state");
96+
Self::default()
97+
}
98+
}
99+
55100
/// Checks if caller is contract owner, panics if not
56101
fn only_owner(&self) {
57102
assert_eq!(env::predecessor_account_id(), self.owner, "Only the owner can call this function");
@@ -301,4 +346,37 @@ mod tests {
301346
// Call verify which should make cross-contract call to the guard
302347
contract.verify("jwt#my-guard.com".to_string(), jwt, sign_payload);
303348
}
349+
350+
#[test]
351+
fn test_update_contract_owner_success() {
352+
let owner = accounts(1);
353+
let context = get_context(owner.clone());
354+
testing_env!(context.build());
355+
356+
let contract = JwtGuardRouter {
357+
guards: LookupMap::new(MAP_KEY),
358+
owner: owner.clone(),
359+
};
360+
361+
// This should not panic for owner (input handling is tested in integration tests)
362+
let _promise = contract.update_contract();
363+
}
364+
365+
#[test]
366+
#[should_panic(expected = "Only the owner can call this function")]
367+
fn test_update_contract_non_owner_fails() {
368+
let owner = accounts(1);
369+
let non_owner = accounts(2);
370+
let context = get_context(non_owner.clone());
371+
testing_env!(context.build());
372+
373+
let contract = JwtGuardRouter {
374+
guards: LookupMap::new(MAP_KEY),
375+
owner: owner.clone(),
376+
};
377+
378+
// This should panic because non-owner is calling
379+
contract.update_contract();
380+
}
381+
304382
}

0 commit comments

Comments
 (0)