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

Fix (audit AUR-03): signer_id for: deposit and withdraw #14

Merged
merged 7 commits into from
Mar 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
129 changes: 95 additions & 34 deletions eth-connector-tests/src/connector.rs

Large diffs are not rendered by default.

61 changes: 22 additions & 39 deletions eth-connector-tests/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,37 +25,16 @@ pub struct TestContract {

impl TestContract {
pub async fn new() -> anyhow::Result<TestContract> {
use std::str::FromStr;

let (contract, root_account) = Self::deploy_aurora_contract().await?;

let prover_account: AccountId = contract.id().clone();
let eth_custodian_address = CUSTODIAN_ADDRESS;
let metadata = Self::metadata_default();
let account_with_access_right: AccountId = AccountId::from_str(CONTRACT_ACC).unwrap();
// Init eth-connector
let res = contract
.call("new")
.args_json((
prover_account,
eth_custodian_address,
metadata,
account_with_access_right,
))
.gas(DEFAULT_GAS)
.transact()
.await?;
assert!(res.is_success());

Ok(Self {
contract,
root_account,
})
Self::new_with_custodian_and_owner(CUSTODIAN_ADDRESS, CONTRACT_ACC).await
}

pub async fn new_with_custodian(eth_custodian_address: &str) -> anyhow::Result<TestContract> {
pub async fn new_with_custodian_and_owner(
eth_custodian_address: &str,
owner_id: &str,
) -> anyhow::Result<TestContract> {
use std::str::FromStr;
let (contract, root_account) = Self::deploy_aurora_contract().await?;
let owner_id: AccountId = AccountId::from_str(owner_id).unwrap();

let prover_account: AccountId = contract.id().clone();
let metadata = Self::metadata_default();
Expand All @@ -66,6 +45,7 @@ impl TestContract {
.args_json(json!({
"prover_account": prover_account,
"account_with_access_right": account_with_access_right,
"owner_id": owner_id,
"eth_custodian_address": eth_custodian_address,
"metadata": metadata,
}))
Expand Down Expand Up @@ -208,21 +188,21 @@ impl TestContract {
.view()
.await?
.borsh::<bool>()
.unwrap();
.expect("call_is_used_proof");
Ok(res)
}

pub async fn call_deposit_eth_to_aurora(&self) -> anyhow::Result<()> {
let proof: Proof = serde_json::from_str(PROOF_DATA_ETH).unwrap();
let res = self.deposit_with_proof(&proof).await?;
assert!(res.is_success());
assert!(res.is_success(), "call_deposit_eth_to_aurora: {:#?}", res);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a nit, asserts or panic messages should be formatted as full sentences with a capital as it makes grammatical sense on the panics in the return in the terminal.

Ok(())
}

pub async fn call_deposit_eth_to_near(&self) -> anyhow::Result<()> {
let proof: Proof = self.get_proof(PROOF_DATA_NEAR);
let res = self.deposit_with_proof(&proof).await?;
assert!(res.is_success());
assert!(res.is_success(), "call_deposit_eth_to_near: {:#?}", res);
Ok(())
}

Expand All @@ -234,7 +214,7 @@ impl TestContract {
.view()
.await?
.json::<U128>()
.unwrap();
.expect("get_eth_on_near_balance");
Ok(res)
}

Expand All @@ -245,7 +225,7 @@ impl TestContract {
.view()
.await?
.json::<U128>()
.unwrap())
.expect("total_supply"))
}

fn metadata_default() -> FungibleTokenMetadata {
Expand Down Expand Up @@ -320,7 +300,12 @@ impl TestContract {
Ok(())
}

pub fn mock_proof(recipient_id: &AccountId, deposit_amount: u128) -> String {
pub fn mock_proof(
&self,
recipient_id: &AccountId,
deposit_amount: u128,
proof_index: u64,
) -> Proof {
use aurora_engine_types::{
types::{Fee, NEP141Wei},
H160, H256, U256,
Expand Down Expand Up @@ -364,21 +349,19 @@ impl TestContract {
ethabi::Token::Uint(U256::from(deposit_event.fee.as_u128())),
]),
};
let data = Proof {
log_index: 1,
Proof {
log_index: proof_index,
// Only this field matters for the purpose of this test
log_entry_data: rlp::encode(&log_entry).to_vec(),
receipt_index: 1,
receipt_data: Vec::new(),
header_data: Vec::new(),
proof: Vec::new(),
};
serde_json::to_string(&data).expect("failed serialize proof")
}
}

pub async fn call_deposit_contract(&self) -> anyhow::Result<()> {
let proof: Proof =
self.get_proof(&Self::mock_proof(self.contract.id(), DEPOSITED_CONTRACT));
let proof: Proof = self.mock_proof(self.contract.id(), DEPOSITED_CONTRACT, 1);
let res = self.deposit_with_proof(&proof).await?;
assert!(res.is_success(), "call_deposit_contract: {:#?}", res);
Ok(())
Expand Down
30 changes: 21 additions & 9 deletions eth-connector/src/admin_controlled.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,15 @@ pub trait AdminControlled {
/// called by owner of the contract.
fn set_paused_flags(&mut self, paused: PausedMask);

/// Return if the contract is paused for the current flag and user
fn is_paused(&self, flag: PausedMask, is_owner: bool) -> bool {
(self.get_paused_flags() & flag) != 0 && !is_owner
/// Return if the contract is paused for the current flag.
/// If it's owner, result always `false` - unpaused.
fn is_paused(&self, flag: PausedMask) -> bool {
(self.get_paused_flags() & flag) != 0 && !self.is_owner()
}

/// Asserts the passed paused flag is not set. Returns `PausedError` if paused.
fn assert_not_paused(
&self,
flag: PausedMask,
is_owner: bool,
) -> Result<(), error::AdminControlledError> {
if self.is_paused(flag, is_owner) {
fn assert_not_paused(&self, flag: PausedMask) -> Result<(), error::AdminControlledError> {
if self.is_paused(flag) {
Err(error::AdminControlledError::Paused)
} else {
Ok(())
Expand All @@ -45,13 +42,28 @@ pub trait AdminControlled {
/// Check access right for predecessor account
fn assert_access_right(&self) -> Result<(), error::AdminControlledError> {
if self.get_access_right() == near_sdk::env::predecessor_account_id()
|| self.is_owner()
|| near_sdk::env::predecessor_account_id() == near_sdk::env::current_account_id()
{
Ok(())
} else {
Err(error::AdminControlledError::AccessRight)
}
}

/// Asseert only owners of contract access right
fn assert_owner_access_right(&self) -> Result<(), error::AdminControlledError> {
if self.is_owner()
|| near_sdk::env::predecessor_account_id() == near_sdk::env::current_account_id()
{
Ok(())
} else {
Err(error::AdminControlledError::AccessRight)
}
}

/// Check is predecessor account ID is owner
fn is_owner(&self) -> bool;
}

pub mod error {
Expand Down
20 changes: 13 additions & 7 deletions eth-connector/src/connector_impl.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
admin_controlled::PAUSE_DEPOSIT,
connector::{ext_funds_finish, ext_proof_verifier, ConnectorDeposit},
connector::{ext_funds_finish, ext_proof_verifier},
deposit_event::{DepositedEvent, TokenMessageData},
errors, log, panic_err,
proof::Proof,
Expand Down Expand Up @@ -58,6 +58,9 @@ pub struct EthConnector {

/// Account with access right for current contract
pub account_with_access_right: AccountId,

/// Owner account ID
pub owner_id: AccountId,
}

impl AdminControlled for EthConnector {
Expand All @@ -76,16 +79,19 @@ impl AdminControlled for EthConnector {
fn get_access_right(&self) -> AccountId {
self.account_with_access_right.clone()
}

fn is_owner(&self) -> bool {
self.owner_id == env::predecessor_account_id()
|| env::current_account_id() == env::predecessor_account_id()
}
}

impl ConnectorDeposit for EthConnector {
fn deposit(&mut self, raw_proof: Proof) -> Promise {
impl EthConnector {
pub(crate) fn deposit(&mut self, raw_proof: Proof) -> Promise {
let current_account_id = env::current_account_id();
let predecessor_account_id = env::predecessor_account_id();
// Check is current account owner
let is_owner = current_account_id == predecessor_account_id;

// Check is current flow paused. If it's owner account just skip it.
self.assert_not_paused(PAUSE_DEPOSIT, is_owner).sdk_unwrap();
self.assert_not_paused(PAUSE_DEPOSIT).sdk_unwrap();

log!("[Deposit tokens]");
let proof = raw_proof.clone();
Expand Down
19 changes: 11 additions & 8 deletions eth-connector/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ impl EthConnectorContract {
eth_custodian_address: String,
metadata: FungibleTokenMetadata,
account_with_access_right: AccountId,
owner_id: AccountId,
) -> Self {
require!(!env::state_exists(), "Already initialized");
metadata.assert_valid();
Expand All @@ -177,6 +178,7 @@ impl EthConnectorContract {
paused_mask,
eth_custodian_address: Address::decode(&eth_custodian_address).unwrap(),
account_with_access_right,
owner_id,
};
let owner_id = env::current_account_id();
let mut this = Self {
Expand Down Expand Up @@ -446,19 +448,23 @@ impl AdminControlled for EthConnectorContract {
self.connector.get_paused_flags()
}

#[private]
fn set_paused_flags(&mut self, #[serializer(borsh)] paused: PausedMask) {
self.connector.assert_owner_access_right().sdk_unwrap();
self.connector.set_paused_flags(paused)
}

#[private]
fn set_access_right(&mut self, account: &AccountId) {
self.connector.assert_owner_access_right().sdk_unwrap();
self.connector.set_access_right(account)
}

fn get_access_right(&self) -> AccountId {
self.connector.get_access_right()
}

fn is_owner(&self) -> bool {
self.connector.is_owner()
}
}

#[near_bindgen]
Expand All @@ -473,12 +479,9 @@ impl ConnectorWithdraw for EthConnectorContract {
) -> WithdrawResult {
self.assert_access_right().sdk_unwrap();
assert_one_yocto();
let predecessor_account_id = env::predecessor_account_id();
let current_account_id = env::current_account_id();
// Check is current account id is owner
let is_owner = current_account_id == predecessor_account_id;
// Check is current flow paused. If it's owner just skip assertion.
self.assert_not_paused(PAUSE_WITHDRAW, is_owner)

// Check is current flow paused. If it's owner just skip asserrion.
self.assert_not_paused(PAUSE_WITHDRAW)
.map_err(|_| "WithdrawErrorPaused")
.sdk_unwrap();
// Burn tokens to recipient
Expand Down