// The main code is at the very bottom.

#[allow(unused_imports)]
#[macro_use]
pub mod spella {
  pub mod byte {
    pub use self::byte_char::*;
    pub use self::byte_str::*;
    pub use self::byte_string::*;
    pub use self::from_byte_str::*;

    mod byte_char {
      use std::fmt::{self, Debug, Display, Formatter};

      #[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
      pub struct ByteChar(pub u8);

      impl Debug for ByteChar {
        fn fmt(&self, f: &mut Formatter) -> fmt::Result {
          write!(f, "b'{}'", self.0 as char)
        }
      }

      impl Display for ByteChar {
        fn fmt(&self, f: &mut Formatter) -> fmt::Result {
          write!(f, "{}", self.0 as char)
        }
      }
    }

    mod byte_str {
      use spella::byte::{ByteChar, ByteString};
      use std::fmt::{self, Debug, Display, Formatter};
      use std::ops::{Deref, DerefMut};

      #[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
      pub struct ByteStr([ByteChar]);

      macro_rules! cast {
        (mut $x:expr, $($T:ty)=>*) => {
          unsafe { &mut *($x $(as *mut $T)*) }
        };
        ($x:expr, $($T:ty)=>*) => {
          unsafe { &*($x $(as *const $T)*) }
        };
      }

      impl ByteStr {
        pub fn from_bytes(s: &[u8]) -> &Self {
          cast!(s, [u8] => [ByteChar] => ByteStr)
        }

        pub fn from_bytes_mut(s: &mut [u8]) -> &mut Self {
          cast!(mut s, [u8] => [ByteChar] => ByteStr)
        }

        pub fn from_byte_chars(s: &[ByteChar]) -> &Self {
          cast!(s, [ByteChar] => ByteStr)
        }

        pub fn from_byte_chars_mut(s: &mut [ByteChar]) -> &mut Self {
          cast!(mut s, [ByteChar] => ByteStr)
        }

        pub fn as_byte_chars(&self) -> &[ByteChar] {
          &self.0
        }

        pub fn as_byte_chars_mut(&mut self) -> &mut [ByteChar] {
          &mut self.0
        }

        pub fn as_bytes(&self) -> &[u8] {
          cast!(self, ByteStr => [ByteChar] => [u8])
        }

        pub fn as_bytes_mut(&mut self) -> &mut [u8] {
          cast!(mut self, ByteStr => [ByteChar] => [u8])
        }
      }

      impl ToOwned for ByteStr {
        type Owned = ByteString;

        fn to_owned(&self) -> ByteString {
          ByteString::from(self.0.to_owned())
        }
      }

      impl Debug for ByteStr {
        fn fmt(&self, f: &mut Formatter) -> fmt::Result {
          write!(f, "b\"")?;

          for &c in &self.0 {
            write!(f, "{}", c)?;
          }

          write!(f, "\"")
        }
      }

      impl Display for ByteStr {
        fn fmt(&self, f: &mut Formatter) -> fmt::Result {
          for &c in &self.0 {
            write!(f, "{}", c)?;
          }

          Ok(())
        }
      }

      impl Deref for ByteStr {
        type Target = [ByteChar];

        fn deref(&self) -> &[ByteChar] {
          self.as_byte_chars()
        }
      }

      impl DerefMut for ByteStr {
        fn deref_mut(&mut self) -> &mut [ByteChar] {
          self.as_byte_chars_mut()
        }
      }
    }

    mod byte_string {
      use spella::byte::{ByteChar, ByteStr};

      use std::borrow::{Borrow, BorrowMut};
      use std::fmt::{self, Debug, Display, Formatter};
      use std::ops::{Deref, DerefMut};

      #[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
      pub struct ByteString(Vec<ByteChar>);

      impl ByteString {
        pub fn into_byte_chars(self) -> Vec<ByteChar> {
          self.0
        }

        pub fn as_byte_str(&self) -> &ByteStr {
          ByteStr::from_byte_chars(&self.0)
        }

        pub fn as_mut_byte_str(&mut self) -> &mut ByteStr {
          ByteStr::from_byte_chars_mut(&mut self.0)
        }
      }

      impl From<Vec<ByteChar>> for ByteString {
        fn from(s: Vec<ByteChar>) -> ByteString {
          ByteString(s)
        }
      }

      impl Borrow<ByteStr> for ByteString {
        fn borrow(&self) -> &ByteStr {
          self.as_byte_str()
        }
      }

      impl BorrowMut<ByteStr> for ByteString {
        fn borrow_mut(&mut self) -> &mut ByteStr {
          self.as_mut_byte_str()
        }
      }

      impl Debug for ByteString {
        fn fmt(&self, f: &mut Formatter) -> fmt::Result {
          Debug::fmt(self.as_byte_str(), f)
        }
      }

      impl Display for ByteString {
        fn fmt(&self, f: &mut Formatter) -> fmt::Result {
          Display::fmt(self.as_byte_str(), f)
        }
      }

      impl Deref for ByteString {
        type Target = ByteStr;

        fn deref(&self) -> &ByteStr {
          ByteStr::from_byte_chars(&self.0)
        }
      }

      impl DerefMut for ByteString {
        fn deref_mut(&mut self) -> &mut ByteStr {
          ByteStr::from_byte_chars_mut(&mut self.0)
        }
      }
    }

    mod from_byte_str {
      use spella::byte::{ByteChar, ByteStr, ByteString};

      use std::error::Error;
      use std::fmt::{self, Debug, Display, Formatter};
      use std::str::{self, FromStr, Utf8Error};

      pub trait FromByteStr: Sized {
        type Err;

        fn from_byte_str(s: &ByteStr) -> Result<Self, Self::Err>;
      }

      macro_rules! fn_description {
        () => {
          fn description(&self) -> &str {
            "description() is deprecated; use Display"
          }
        };
      }

      #[derive(Debug)]
      pub struct ParseByteCharError(ParseByteCharErrorKind);

      #[derive(Debug)]
      enum ParseByteCharErrorKind {
        EmptyByteStr,
        TooManyByteChars,
      }

      impl Display for ParseByteCharError {
        fn fmt(&self, f: &mut Formatter) -> fmt::Result {
          use self::ParseByteCharErrorKind::*;

          f.write_str(match self.0 {
            EmptyByteStr => "empty `ByteStr`",
            TooManyByteChars => "too many `ByteChar`s",
          })
        }
      }

      impl Error for ParseByteCharError {
        fn_description! {}
      }

      impl FromByteStr for ByteChar {
        type Err = ParseByteCharError;

        fn from_byte_str(s: &ByteStr) -> Result<Self, Self::Err> {
          use self::ParseByteCharErrorKind::*;

          match s.len() {
            1 => Ok(unsafe { *s.get_unchecked(0) }),
            0 => Err(ParseByteCharError(EmptyByteStr)),
            _ => Err(ParseByteCharError(TooManyByteChars)),
          }
        }
      }

      #[derive(Debug)]
      pub enum ParseByteStringError {}

      impl Display for ParseByteStringError {
        fn fmt(&self, _: &mut Formatter) -> fmt::Result {
          match *self {}
        }
      }

      impl Error for ParseByteStringError {
        fn_description! {}
      }

      impl FromByteStr for ByteString {
        type Err = ParseByteStringError;

        fn from_byte_str(s: &ByteStr) -> Result<Self, Self::Err> {
          Ok(ByteString::from(s.to_vec()))
        }
      }

      pub struct ParseFromStrError<T: FromStr>(ParseFromStrErrorKind<T>);

      enum ParseFromStrErrorKind<T: FromStr> {
        Utf8Error(Utf8Error),
        FromStrError(T::Err),
      }

      impl<T: FromStr> Debug for ParseFromStrError<T>
      where
        T::Err: Debug,
      {
        fn fmt(&self, f: &mut Formatter) -> fmt::Result {
          use self::ParseFromStrErrorKind::*;

          match self.0 {
            Utf8Error(ref err) => f.debug_tuple("Utf8Error").field(err).finish(),
            FromStrError(ref err) => f.debug_tuple("FromStrError").field(err).finish(),
          }
        }
      }

      impl<T: FromStr> Display for ParseFromStrError<T>
      where
        T::Err: Display,
      {
        fn fmt(&self, f: &mut Formatter) -> fmt::Result {
          use self::ParseFromStrErrorKind::*;

          match self.0 {
            Utf8Error(ref err) => write!(f, "{}", err),
            FromStrError(ref err) => write!(f, "{}", err),
          }
        }
      }

      impl<T: FromStr> Error for ParseFromStrError<T>
      where
        T::Err: Debug + Display,
      {
        fn_description! {}
      }

      impl<T: FromStr> FromByteStr for T {
        type Err = ParseFromStrError<T>;

        fn from_byte_str(s: &ByteStr) -> Result<T, Self::Err> {
          use self::ParseFromStrErrorKind::*;

          str::from_utf8(s.as_bytes())
            .map_err(|e| ParseFromStrError(Utf8Error(e)))
            .and_then(|s| s.parse().map_err(|e| ParseFromStrError(FromStrError(e))))
        }
      }
    }
  }

  pub mod io {
    pub use self::scanner::*;

    mod scanner {
      use spella::byte::{ByteStr, FromByteStr};

      use std::io::{self, BufRead};

      #[derive(Debug)]
      pub struct Scanner<R> {
        reader: R,
        buf: Vec<u8>,
        pos: usize,
      }

      const INITIAL_CAPACITY: usize = 32;

      impl<R: BufRead> Scanner<R> {
        pub fn new(reader: R) -> Self {
          Scanner {
            reader: reader,
            buf: Vec::with_capacity(INITIAL_CAPACITY),
            pos: 0,
          }
        }

        pub fn next<T: FromByteStr>(&mut self) -> io::Result<Result<T, T::Err>> {
          self.next_byte_str().map(T::from_byte_str)
        }

        pub fn next_byte_str(&mut self) -> io::Result<&ByteStr> {
          if self.buf.is_empty() {
            self.read_line()?;
          }

          loop {
            match self.buf.get(self.pos) {
              Some(&b' ') => self.pos += 1,
              Some(&b'\n') => self.read_line()?,
              Some(_) => break,
              None => return Err(io::Error::from(io::ErrorKind::UnexpectedEof)),
            }
          }

          let start = self.pos;
          self.pos += 1;

          loop {
            match self.buf.get(self.pos) {
              Some(&b' ') | Some(&b'\n') | None => break,
              Some(_) => self.pos += 1,
            }
          }

          Ok(ByteStr::from_bytes(&self.buf[start..self.pos]))
        }

        fn read_line(&mut self) -> io::Result<()> {
          self.buf.clear();
          self.pos = 0;
          self.reader.read_until(b'\n', &mut self.buf)?;

          Ok(())
        }
      }
    }
  }
}

pub mod stopgap {
  use std::fmt::{self, Debug, Display, Formatter};
  use std::hash::{Hash, Hasher};
  use std::iter::{Product, Sum};
  use std::marker::PhantomData;
  use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};

  pub trait Constant<T> {
    fn get() -> T;
  }

  pub enum _1000000007 {}

  impl Constant<u32> for _1000000007 {
    fn get() -> u32 {
      1000000007
    }
  }

  pub struct ModU32<Mod> {
    _phantom: PhantomData<fn() -> Mod>,
    repr: u32,
  }

  impl<Mod: Constant<u32>> Clone for ModU32<Mod> {
    fn clone(&self) -> Self {
      Self::from_repr(self.repr)
    }
  }

  impl<Mod: Constant<u32>> Copy for ModU32<Mod> {}

  impl<Mod: Constant<u32>> PartialEq for ModU32<Mod> {
    fn eq(&self, other: &Self) -> bool {
      self.repr.eq(&other.repr)
    }
  }

  impl<Mod: Constant<u32>> Eq for ModU32<Mod> {}

  impl<Mod: Constant<u32>> Default for ModU32<Mod> {
    fn default() -> Self {
      ModU32::from_repr(u32::default())
    }
  }

  impl<Mod: Constant<u32>> Hash for ModU32<Mod> {
    fn hash<H>(&self, state: &mut H)
    where
      H: Hasher,
    {
      self.repr.hash(state);
    }
  }

  impl<Mod: Constant<u32>> Debug for ModU32<Mod> {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
      Debug::fmt(&self.repr, f)
    }
  }

  impl<Mod: Constant<u32>> Display for ModU32<Mod> {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
      Display::fmt(&self.repr, f)
    }
  }

  impl<Mod: Constant<u32>> ModU32<Mod> {
    pub fn new(value: u32) -> Self {
      Self::from_repr(value % Mod::get())
    }

    fn from_repr(repr: u32) -> Self {
      debug_assert!(repr < Mod::get());
      ModU32 {
        _phantom: PhantomData,
        repr: repr,
      }
    }

    pub fn repr(self) -> u32 {
      self.repr
    }

    pub fn inverse(self) -> Self {
      let mut a = self.repr as i64;
      let mut b = Mod::get() as i64;
      let mut u = 1i64;
      let mut v = 0i64;
      while b != 0 {
        let t = a / b;
        a -= t * b;
        ::std::mem::swap(&mut a, &mut b);
        u -= t * v;
        ::std::mem::swap(&mut u, &mut v);
      }
      u %= Mod::get() as i64;
      if u < 0 {
        u += Mod::get() as i64;
      }
      Self::from_repr(u as u32)
    }

    pub fn pow(mut self, mut n: u32) -> Self {
      let mut acc = ModU32::from_repr(1);
      while n > 0 {
        if n % 2 == 1 {
          acc *= self;
        }
        self *= self;
        n /= 2;
      }
      acc
    }
  }

  impl<Mod: Constant<u32>> Add for ModU32<Mod> {
    type Output = Self;

    fn add(mut self, rhs: Self) -> Self {
      self += rhs;
      self
    }
  }

  impl<Mod: Constant<u32>> AddAssign for ModU32<Mod> {
    fn add_assign(&mut self, rhs: Self) {
      self.repr += rhs.repr;
      if self.repr >= Mod::get() {
        self.repr -= Mod::get();
      }
    }
  }

  impl<Mod: Constant<u32>> Sub for ModU32<Mod> {
    type Output = Self;

    fn sub(mut self, rhs: Self) -> Self {
      self -= rhs;
      self
    }
  }

  impl<Mod: Constant<u32>> SubAssign for ModU32<Mod> {
    fn sub_assign(&mut self, rhs: Self) {
      if self.repr < rhs.repr {
        self.repr += Mod::get();
      }
      self.repr -= rhs.repr;
    }
  }

  impl<Mod: Constant<u32>> Mul for ModU32<Mod> {
    type Output = Self;

    fn mul(self, rhs: Self) -> Self {
      Self::from_repr((self.repr as u64 * rhs.repr as u64 % Mod::get() as u64) as u32)
    }
  }

  impl<Mod: Constant<u32>> MulAssign for ModU32<Mod> {
    fn mul_assign(&mut self, rhs: Self) {
      *self = *self * rhs;
    }
  }

  impl<Mod: Constant<u32>> Div for ModU32<Mod> {
    type Output = Self;

    fn div(self, rhs: Self) -> Self {
      self * rhs.inverse()
    }
  }

  impl<Mod: Constant<u32>> DivAssign for ModU32<Mod> {
    fn div_assign(&mut self, rhs: Self) {
      *self *= rhs.inverse();
    }
  }

  impl<Mod: Constant<u32>> Sum for ModU32<Mod> {
    fn sum<I>(iter: I) -> Self
    where
      I: Iterator<Item = Self>,
    {
      let mut acc = ModU32::from_repr(0);
      for x in iter {
        acc += x;
      }
      acc
    }
  }

  impl<Mod: Constant<u32>> Product for ModU32<Mod> {
    fn product<I>(iter: I) -> Self
    where
      I: Iterator<Item = Self>,
    {
      let mut acc = ModU32::from_repr(1);
      for x in iter {
        acc *= x;
      }
      acc
    }
  }

  pub fn mod_pow<Mod: Constant<u32>>(base: u32, n: u32) -> ModU32<Mod> {
    ModU32::new(base).pow(n)
  }

  pub fn mod_binomial<Mod: Constant<u32>>(mut n: u32, mut k: u32) -> ModU32<Mod> {
    let mut acc = ModU32::from_repr(1);
    while k != 0 {
      {
        let n = n % Mod::get();
        let k = k % Mod::get();
        if n < k {
          return ModU32::from_repr(0);
        }
        acc *= (n - k + 1..n + 1).map(ModU32::from_repr).product();
        acc /= (1..k + 1).map(ModU32::from_repr).product();
      }
      n /= Mod::get();
      k /= Mod::get();
    }
    acc
  }
}

#[allow(unused_imports)]
use stopgap::*;

mod prelude {
  pub use spella::byte::{ByteChar, ByteStr, ByteString};
  pub use std::collections::*;
  pub use std::io::prelude::*;
  pub use std::iter::FromIterator;
  pub use std::marker::PhantomData;
  pub use std::num::Wrapping;
  pub use std::ops::{Range, RangeFrom, RangeTo};
  pub use std::{cell, cmp, f64, i32, i64, isize, iter, mem, rc, str, time, u32, u64, usize};
}

use prelude::*;

const CUSTOM_STACK_SIZE_MEBIBYTES: Option<usize> = None;

fn main() {
  fn exec_solver() {
    let stdin = std::io::stdin();
    let stdout = std::io::stdout();
    #[cfg(not(debug_assertions))]
    let mut writer = std::io::BufWriter::new(stdout.lock());
    #[cfg(debug_assertions)]
    let mut writer = stdout.lock();
    solve(stdin.lock(), &mut writer);
    writer.flush().unwrap();
  }

  if let Some(stack_size_mebibytes) = CUSTOM_STACK_SIZE_MEBIBYTES {
    std::thread::Builder::new()
      .name("exec_solver".to_owned())
      .stack_size(stack_size_mebibytes * 1024 * 1024)
      .spawn(exec_solver)
      .unwrap()
      .join()
      .unwrap();
  } else {
    exec_solver();
  }
}

fn solve<R: BufRead, W: Write>(reader: R, mut writer: W) {
  let mut _scanner = spella::io::Scanner::new(reader);

  #[allow(unused_macros)]
  macro_rules! scan {
    ($T:ty) => {
      _scanner.next::<$T>().unwrap().unwrap()
    };
    ($($T:ty),+) => {
      ($(scan!($T)),+)
    };
    ($($T:ty),+; $n:expr $(; $m:expr)*) => {{
      (0..$n).map(|_| scan!($($T),+ $(; $m)*)).collect::<Vec<_>>()
    }};
  }

  #[allow(unused_macros)]
  macro_rules! scan_iter {
    ($($T:ty),+; $n:expr) => {
      (0..$n).map(|_| scan!($($T),+))
    };
  }

  #[allow(unused_macros)]
  macro_rules! print {
    ($fmt:expr) => {
      write!(writer, $fmt).unwrap()
    };
    ($fmt:expr, $($arg:tt)*) => {
      write!(writer, $fmt, $($arg)*).unwrap()
    };
  }

  #[allow(unused_macros)]
  macro_rules! println {
    ($fmt:expr) => {
      writeln!(writer, $fmt).unwrap()
    };
    ($fmt:expr, $($arg:tt)*) => {
      writeln!(writer, $fmt, $($arg)*).unwrap()
    };
  }

  #[allow(unused_macros)]
  macro_rules! eprint {
    ($fmt:expr) => {
      #[cfg(debug_assertions)]
      write!(std::io::stderr(), $fmt).unwrap()
    };
    ($fmt:expr, $($arg:tt)*) => {
      #[cfg(debug_assertions)]
      write!(std::io::stderr(), $fmt, $($arg)*).unwrap()
    };
  }

  #[allow(unused_macros)]
  macro_rules! eprintln {
    ($fmt:expr) => {
      #[cfg(debug_assertions)]
      writeln!(std::io::stderr(), $fmt).unwrap()
    };
    ($fmt:expr, $($arg:tt)*) => {
      #[cfg(debug_assertions)]
      writeln!(std::io::stderr(), $fmt, $($arg)*).unwrap()
    };
  }

  #[allow(unused_macros)]
  macro_rules! dbg {
    ($($x:expr),+) => {{
      eprintln!(concat!("[{}:{}] ", $(stringify!($x), " = {:?}; "),+), file!(), line!(), $($x),+);
    }};
  }

  let (n, m) = scan!(u32, usize);
  let sc = scan!(u32, u32; m);

  if (n, m) == (1, 0) {
    println!("{}", 0);
    return;
  }

  for i in 0..m {
    for j in 0..m {
      if i != j && sc[i].0 == sc[j].0 && sc[i].1 != sc[j].1 {
        println!("-1");
        return;
      }
    }
  }

  let mut ans = vec![None; n as usize];
  for &(s, c) in &sc {
    ans[(n - s) as usize] = Some(c);
  }

  if ans[n as usize - 1] == Some(0) {
    if n == 1 {
      println!("{}", 0);
    } else {
      println!("-1");
    }
    return;
  }

  let mut ok = false;
  for &d in ans.iter().rev() {
    let d = if ok {
      d.unwrap_or(0)
    } else {
      ok = true;
      if let Some(d) = d {
        d
      } else {
        1
      }
    };
    print!("{}", d);
  }
  println!("");
}


Measured memory_bytes: 4352