Skip to content

Commit

Permalink
Merge pull request #99 from Chia-Network/amount-exceed-maximum
Browse files Browse the repository at this point in the history
Introduce AmountExceedsMaximum error code
  • Loading branch information
arvidn authored Dec 5, 2022
2 parents 2978a85 + 2a36f7a commit 2b3d3b7
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 27 deletions.
41 changes: 39 additions & 2 deletions src/gen/condition_sanitizers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,18 @@ pub fn parse_amount(a: &Allocator, n: NodePtr, code: ErrorCode) -> Result<u64, V
// amounts are not allowed to exceed 2^64. i.e. 8 bytes
match sanitize_uint(a, n, 8, code) {
Err(ValidationErr(n, ErrorCode::NegativeAmount)) => Err(ValidationErr(n, code)),
Err(ValidationErr(n, ErrorCode::AmountExceedsMaximum)) => Err(ValidationErr(n, code)),
Err(r) => Err(r),
Ok(r) => Ok(u64_from_bytes(r)),
}
}

pub fn parse_create_coin_amount(a: &Allocator, n: NodePtr) -> Result<u64, ValidationErr> {
// amounts are not allowed to exceed 2^64. i.e. 8 bytes
let buf = sanitize_uint(a, n, 8, ErrorCode::InvalidCoinAmount)?;
Ok(u64_from_bytes(buf))
}

// a negative height is always true. In this case the
// condition can be ignored and this functon returns 0
pub fn parse_height(a: &Allocator, n: NodePtr, code: ErrorCode) -> Result<u32, ValidationErr> {
Expand All @@ -35,6 +42,7 @@ pub fn parse_height(a: &Allocator, n: NodePtr, code: ErrorCode) -> Result<u32, V
// Height is always positive, so a negative requirement is always true,
// just like 0.
Err(ValidationErr(_, ErrorCode::NegativeAmount)) => Ok(0),
Err(ValidationErr(n, ErrorCode::AmountExceedsMaximum)) => Err(ValidationErr(n, code)),
Err(r) => Err(r),
Ok(r) => Ok(u64_from_bytes(r) as u32),
}
Expand All @@ -47,6 +55,7 @@ pub fn parse_seconds(a: &Allocator, n: NodePtr, code: ErrorCode) -> Result<u64,
// seconds is always positive, so a negative requirement is always true,
// we don't need to include this condition
Err(ValidationErr(_, ErrorCode::NegativeAmount)) => Ok(0),
Err(ValidationErr(n, ErrorCode::AmountExceedsMaximum)) => Err(ValidationErr(n, code)),
Err(r) => Err(r),
Ok(r) => Ok(u64_from_bytes(r)),
}
Expand Down Expand Up @@ -140,7 +149,6 @@ fn amount_tester(buf: &[u8]) -> Result<u64, ValidationErr> {
#[test]
fn test_sanitize_amount() {
// negative amounts are not allowed
// regardless of flags
assert_eq!(
amount_tester(&[0x80]).unwrap_err().1,
ErrorCode::InvalidCoinAmount
Expand All @@ -156,7 +164,7 @@ fn test_sanitize_amount() {

// leading zeros are somtimes necessary to make values positive
assert_eq!(amount_tester(&[0, 0xff]), Ok(0xff));
// but are stripped when they are redundant
// but are disallowed when they are redundant
assert_eq!(
amount_tester(&[0, 0, 0, 0xff]).unwrap_err().1,
ErrorCode::InvalidCoinAmount
Expand Down Expand Up @@ -189,6 +197,35 @@ fn test_sanitize_amount() {
);
}

#[cfg(test)]
fn create_amount_tester(buf: &[u8]) -> Result<u64, ValidationErr> {
let mut a = Allocator::new();
let n = a.new_atom(buf).unwrap();

parse_create_coin_amount(&mut a, n)
}

#[test]
fn test_sanitize_create_coin_amount() {
// negative coin amounts are not allowed
assert_eq!(
create_amount_tester(&[0xff]).unwrap_err().1,
ErrorCode::NegativeAmount
);
// amounts aren't allowed to be too big
let large_buf = [0x7f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
assert_eq!(
create_amount_tester(&large_buf).unwrap_err().1,
ErrorCode::AmountExceedsMaximum
);

// this is small enough though
assert_eq!(
create_amount_tester(&[0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]),
Ok(0xffffffffffffffff)
);
}

#[cfg(test)]
fn height_tester(buf: &[u8]) -> Result<u32, ValidationErr> {
let mut a = Allocator::new();
Expand Down
44 changes: 20 additions & 24 deletions src/gen/conditions.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use super::coin_id::compute_coin_id;
use super::condition_sanitizers::{
parse_amount, parse_height, parse_seconds, sanitize_announce_msg, sanitize_hash,
parse_amount, parse_create_coin_amount, parse_height, parse_seconds, sanitize_announce_msg,
sanitize_hash,
};
use super::opcodes::{
parse_opcode, ConditionOpcode, AGG_SIG_COST, AGG_SIG_ME, AGG_SIG_UNSAFE, ALWAYS_TRUE,
Expand Down Expand Up @@ -132,7 +133,7 @@ pub fn parse_args(
CREATE_COIN => {
let puzzle_hash = sanitize_hash(a, first(a, c)?, 32, ErrorCode::InvalidPuzzleHash)?;
c = rest(a, c)?;
let amount = parse_amount(a, first(a, c)?, ErrorCode::InvalidCoinAmount)?;
let amount = parse_create_coin_amount(a, first(a, c)?)?;
// CREATE_COIN takes an optional 3rd parameter, which is a list of
// byte buffers (typically a 32 byte hash). We only pull out the
// first element for now, but may support more in the future.
Expand Down Expand Up @@ -235,29 +236,27 @@ pub fn parse_args(
if (flags & STRICT_ARGS_COUNT) != 0 {
check_nil(a, rest(a, c)?)?;
}
Ok(Condition::AssertSecondsRelative(parse_seconds(
a,
first(a, c)?,
ErrorCode::AssertSecondsRelative,
)?))
let seconds = parse_seconds(a, first(a, c)?, ErrorCode::AssertSecondsRelative)?;
Ok(Condition::AssertSecondsRelative(seconds))
}
ASSERT_SECONDS_ABSOLUTE => {
if (flags & STRICT_ARGS_COUNT) != 0 {
check_nil(a, rest(a, c)?)?;
}
Ok(Condition::AssertSecondsAbsolute(parse_seconds(
a,
first(a, c)?,
ErrorCode::AssertSecondsAbsolute,
)?))
let seconds = parse_seconds(a, first(a, c)?, ErrorCode::AssertSecondsAbsolute)?;
Ok(Condition::AssertSecondsAbsolute(seconds))
}
ASSERT_HEIGHT_RELATIVE => {
if (flags & STRICT_ARGS_COUNT) != 0 {
check_nil(a, rest(a, c)?)?;
}
match sanitize_uint(a, first(a, c)?, 4, ErrorCode::AssertHeightRelative) {
let code = ErrorCode::AssertHeightRelative;
match sanitize_uint(a, first(a, c)?, 4, code) {
// Height is always positive, so a negative requirement is always true,
Err(ValidationErr(_, ErrorCode::NegativeAmount)) => Ok(Condition::Skip),
Err(ValidationErr(n, ErrorCode::AmountExceedsMaximum)) => {
Err(ValidationErr(n, code))
}
Err(r) => Err(r),
Ok(r) => Ok(Condition::AssertHeightRelative(u64_from_bytes(r) as u32)),
}
Expand All @@ -266,11 +265,8 @@ pub fn parse_args(
if (flags & STRICT_ARGS_COUNT) != 0 {
check_nil(a, rest(a, c)?)?;
}
Ok(Condition::AssertHeightAbsolute(parse_height(
a,
first(a, c)?,
ErrorCode::AssertHeightAbsolute,
)?))
let height = parse_height(a, first(a, c)?, ErrorCode::AssertHeightAbsolute)?;
Ok(Condition::AssertHeightAbsolute(height))
}
ALWAYS_TRUE => {
// this condition is always true, we always ignore arguments
Expand Down Expand Up @@ -397,10 +393,10 @@ fn parse_spend_conditions(
spend = rest(a, spend)?;
let puzzle_hash = sanitize_hash(a, first(a, spend)?, 32, ErrorCode::InvalidPuzzleHash)?;
spend = rest(a, spend)?;
let amount_buf = sanitize_uint(a, first(a, spend)?, 8, ErrorCode::InvalidCoinAmount)?.to_vec();
let my_amount = u64_from_bytes(&amount_buf);
let my_amount = parse_amount(a, first(a, spend)?, ErrorCode::InvalidCoinAmount)?;
let cond = rest(a, spend)?;
let coin_id = Arc::new(compute_coin_id(a, parent_id, puzzle_hash, &amount_buf));
let amount_buf = a.atom(first(a, spend)?);
let coin_id = Arc::new(compute_coin_id(a, parent_id, puzzle_hash, amount_buf));

if !state.spent_coins.insert(coin_id.clone()) {
// if this coin ID has already been added to this set, it's a double
Expand Down Expand Up @@ -2050,7 +2046,7 @@ fn test_create_coin_amount_exceeds_max() {
cond_test("((({h1} ({h2} (123 (((51 ({h2} (0x010000000000000000 )))))")
.unwrap_err()
.1,
ErrorCode::InvalidCoinAmount
ErrorCode::AmountExceedsMaximum
);
}

Expand All @@ -2061,7 +2057,7 @@ fn test_create_coin_negative_amount() {
cond_test("((({h1} ({h2} (123 (((51 ({h2} (-1 )))))")
.unwrap_err()
.1,
ErrorCode::InvalidCoinAmount
ErrorCode::NegativeAmount
);
}

Expand Down Expand Up @@ -2679,7 +2675,7 @@ fn test_single_spend_negative_amount() {
// the coin we're trying to spend has a negative amount (i.e. it's invalid)
assert_eq!(
cond_test("((({h1} ({h2} (-123 ())))").unwrap_err().1,
ErrorCode::NegativeAmount
ErrorCode::InvalidCoinAmount
);
}

Expand Down
8 changes: 7 additions & 1 deletion src/gen/sanitize_int.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub fn sanitize_uint(

// if there are too many bytes left in the value, it's too big
if buf.len() > size_limit {
return Err(ValidationErr(n, code));
return Err(ValidationErr(n, ErrorCode::AmountExceedsMaximum));
}

Ok(buf)
Expand Down Expand Up @@ -87,4 +87,10 @@ fn test_sanitize_uint() {
sanitize_uint(&a, a1, 8, e).unwrap_err().1,
ErrorCode::InvalidCoinAmount
);

let exceed_maximum = a.new_substr(atom, 100, 110).unwrap();
assert_eq!(
sanitize_uint(&a, exceed_maximum, 8, e).unwrap_err().1,
ErrorCode::AmountExceedsMaximum
);
}
2 changes: 2 additions & 0 deletions src/gen/validation_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use clvmr::allocator::{Allocator, NodePtr, SExp};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorCode {
NegativeAmount,
AmountExceedsMaximum,
InvalidConditionOpcode,
InvalidParentId,
InvalidPuzzleHash,
Expand Down Expand Up @@ -44,6 +45,7 @@ impl From<ErrorCode> for u32 {
fn from(err: ErrorCode) -> u32 {
match err {
ErrorCode::NegativeAmount => 124,
ErrorCode::AmountExceedsMaximum => 16,
ErrorCode::InvalidPuzzleHash => 10,
ErrorCode::InvalidPubkey => 10,
ErrorCode::InvalidMessage => 10,
Expand Down

0 comments on commit 2b3d3b7

Please sign in to comment.