diff --git a/substrate/frame/revive/fixtures/contracts/Arithmetic.sol b/substrate/frame/revive/fixtures/contracts/Arithmetic.sol new file mode 100644 index 0000000000000..7d9345ced2df8 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/Arithmetic.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract Arithmetic { + + function add(uint a, uint b) public view returns (uint) { + return a + b; + } + + function mul(uint a, uint b) public view returns (uint) { + return a * b; + } + + function sub(uint a, uint b) public view returns (uint) { + return a - b; + } + + function div(uint a, uint b) public view returns (uint) { + return a / b; + } + + function sdiv(int a, int b) public view returns (int) { + return a / b; + } + + function rem(uint a, uint b) public view returns (uint) { + return a % b; + } + + function smod(int a, int b) public view returns (int) { + return a % b; + } + + // MOD instruction - unsigned modulo (alternative name to avoid Rust keyword conflict) + function umod(uint a, uint b) public view returns (uint) { + return a % b; + } + + // ADDMOD instruction: (a + b) % n + function addmod(uint a, uint b, uint n) public view returns (uint) { + return (a + b) % n; + } + + // MULMOD instruction: (a * b) % n + function mulmod(uint a, uint b, uint n) public view returns (uint) { + return (a * b) % n; + } + + // EXP instruction: a ** b (exponentiation) + function exp(uint a, uint b) public view returns (uint) { + return a ** b; + } + + // SIGNEXTEND instruction: sign-extend value from (i+1)*8 bits to 256 bits + function signextend(uint i, uint x) public pure returns (uint) { + assembly { + x := signextend(i, x) + } + return x; + } + +} diff --git a/substrate/frame/revive/src/tests/sol.rs b/substrate/frame/revive/src/tests/sol.rs index 15b52ebc4a823..421d31b284983 100644 --- a/substrate/frame/revive/src/tests/sol.rs +++ b/substrate/frame/revive/src/tests/sol.rs @@ -15,6 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +mod arithmetic; mod block_info; mod misc; mod system; diff --git a/substrate/frame/revive/src/tests/sol/arithmetic.rs b/substrate/frame/revive/src/tests/sol/arithmetic.rs new file mode 100644 index 0000000000000..53f0011dd7c92 --- /dev/null +++ b/substrate/frame/revive/src/tests/sol/arithmetic.rs @@ -0,0 +1,686 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! The pallet-revive shared VM integration test suite. + +use crate::{ + test_utils::{builder::Contract, ALICE}, + tests::{builder, ExtBuilder, Test}, + Code, Config, +}; + +use alloy_core::{primitives::U256, primitives::I256, sol_types::SolInterface}; +use frame_support::traits::fungible::Mutate; +use pallet_revive_fixtures::{compile_module_with_type, Arithmetic, FixtureType}; +use pretty_assertions::assert_eq; + +#[test] +fn add_works() { + for fixture_type in [FixtureType::Solc, FixtureType::Resolc] { + let (code, _) = compile_module_with_type("Arithmetic", fixture_type).unwrap(); + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + { + let result = builder::bare_call(addr) + .data( + Arithmetic::ArithmeticCalls::add(Arithmetic::addCall { a: U256::from(20u32), b: U256::from(22u32) }) + .abi_encode(), + ) + .build_and_unwrap_result(); + assert_eq!( + U256::from(42u32), + U256::from_be_bytes::<32>(result.data.try_into().unwrap()), + "ADD(20, 22) should equal 42 for {:?}", fixture_type + ); + } + + { + // Test large numbers but not MAX overflow + let large_a = U256::from(u64::MAX); + let large_b = U256::from(1000u32); + let expected = large_a + large_b; + let result = builder::bare_call(addr) + .data( + Arithmetic::ArithmeticCalls::add(Arithmetic::addCall { a: large_a, b: large_b }) + .abi_encode(), + ) + .build_and_unwrap_result(); + assert_eq!( + expected, + U256::from_be_bytes::<32>(result.data.try_into().unwrap()), + "ADD({}, {}) should equal {} for {:?}", large_a, large_b, expected, fixture_type + ); + } + }); + } +} + +#[test] +fn mul_works() { + for fixture_type in [FixtureType::Solc, FixtureType::Resolc] { + let (code, _) = compile_module_with_type("Arithmetic", fixture_type).unwrap(); + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + { + let result = builder::bare_call(addr) + .data( + Arithmetic::ArithmeticCalls::mul(Arithmetic::mulCall { a: U256::from(20u32), b: U256::from(22u32) }) + .abi_encode(), + ) + .build_and_unwrap_result(); + assert_eq!( + U256::from(440u32), + U256::from_be_bytes::<32>(result.data.try_into().unwrap()), + "MUL(20, 22) should equal 440 for {:?}", fixture_type + ); + } + + { + // Test large numbers but not MAX overflow + let large_a = U256::from(u64::MAX); + let large_b = U256::from(1000u32); + let expected = large_a * large_b; + let result = builder::bare_call(addr) + .data( + Arithmetic::ArithmeticCalls::mul(Arithmetic::mulCall { a: large_a, b: large_b }) + .abi_encode(), + ) + .build_and_unwrap_result(); + assert_eq!( + expected, + U256::from_be_bytes::<32>(result.data.try_into().unwrap()), + "MUL({}, {}) should equal {} for {:?}", large_a, large_b, expected, fixture_type + ); + } + }); + } +} + +#[test] +fn sub_works() { + for fixture_type in [FixtureType::Solc, FixtureType::Resolc] { + let (code, _) = compile_module_with_type("Arithmetic", fixture_type).unwrap(); + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + { + let result = builder::bare_call(addr) + .data( + Arithmetic::ArithmeticCalls::sub(Arithmetic::subCall { a: U256::from(20u32), b: U256::from(18u32) }) + .abi_encode(), + ) + .build_and_unwrap_result(); + assert_eq!( + U256::from(2u32), + U256::from_be_bytes::<32>(result.data.try_into().unwrap()), + "SUB(20, 18) should equal 2 for {:?}", fixture_type + ); + } + + { + // Test large numbers but not MAX overflow + let large_a = U256::from(u64::MAX); + let large_b = U256::from(1000u32); + let expected = large_a - large_b; + let result = builder::bare_call(addr) + .data( + Arithmetic::ArithmeticCalls::sub(Arithmetic::subCall { a: large_a, b: large_b }) + .abi_encode(), + ) + .build_and_unwrap_result(); + assert_eq!( + expected, + U256::from_be_bytes::<32>(result.data.try_into().unwrap()), + "SUB({}, {}) should equal {} for {:?}", large_a, large_b, expected, fixture_type + ); + } + }); + } +} + +#[test] +fn div_works() { + for fixture_type in [FixtureType::Solc, FixtureType::Resolc] { + let (code, _) = compile_module_with_type("Arithmetic", fixture_type).unwrap(); + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + { + let result = builder::bare_call(addr) + .data( + Arithmetic::ArithmeticCalls::div(Arithmetic::divCall { a: U256::from(20u32), b: U256::from(5u32) }) + .abi_encode(), + ) + .build_and_unwrap_result(); + assert_eq!( + U256::from(4u32), + U256::from_be_bytes::<32>(result.data.try_into().unwrap()), + "DIV(20, 5) should equal 4 for {:?}", fixture_type + ); + } + + { + // Test large numbers but not MAX overflow + let large_a = U256::from(u64::MAX); + let large_b = U256::from(1000u32); + let expected = large_a / large_b; + let result = builder::bare_call(addr) + .data( + Arithmetic::ArithmeticCalls::div(Arithmetic::divCall { a: large_a, b: large_b }) + .abi_encode(), + ) + .build_and_unwrap_result(); + assert_eq!( + expected, + U256::from_be_bytes::<32>(result.data.try_into().unwrap()), + "DIV({}, {}) should equal {} for {:?}", large_a, large_b, expected, fixture_type + ); + } + }); + } +} + +#[test] +fn sdiv_works() { + for fixture_type in [FixtureType::Solc, FixtureType::Resolc] { + let (code, _) = compile_module_with_type("Arithmetic", fixture_type).unwrap(); + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + { + let result = builder::bare_call(addr) + .data( + Arithmetic::ArithmeticCalls::sdiv(Arithmetic::sdivCall { a: I256::from_raw(U256::from(20u32)), b: I256::from_raw(U256::from(5u32)) }) + .abi_encode(), + ) + .build_and_unwrap_result(); + assert_eq!( + I256::from_raw(U256::from(4u32)), + I256::from_be_bytes::<32>(result.data.try_into().unwrap()), + "SDIV(20, 5) should equal 4 for {:?}", fixture_type + ); + } + + { + // Test large numbers but not MAX overflow + let large_a = I256::from_raw(U256::from(i64::MAX as u64)); + let large_b = -I256::from_raw(U256::from(1000u32)); + let expected = large_a / large_b; + let result = builder::bare_call(addr) + .data( + Arithmetic::ArithmeticCalls::sdiv(Arithmetic::sdivCall { a: large_a, b: large_b }) + .abi_encode(), + ) + .build_and_unwrap_result(); + assert_eq!( + expected, + I256::from_be_bytes::<32>(result.data.try_into().unwrap()), + "SDIV({}, {}) should equal {} for {:?}", large_a, large_b, expected, fixture_type + ); + } + }); + } +} + +#[test] +fn rem_works() { + for fixture_type in [FixtureType::Solc, FixtureType::Resolc] { + let (code, _) = compile_module_with_type("Arithmetic", fixture_type).unwrap(); + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + { + let result = builder::bare_call(addr) + .data( + Arithmetic::ArithmeticCalls::rem(Arithmetic::remCall { a: U256::from(20u32), b: U256::from(5u32) }) + .abi_encode(), + ) + .build_and_unwrap_result(); + assert_eq!( + U256::from(0u32), + U256::from_be_bytes::<32>(result.data.try_into().unwrap()), + "REM(20, 5) should equal 0 for {:?}", fixture_type + ); + } + + { + // Test with remainder: 23 % 5 = 3 + let result = builder::bare_call(addr) + .data( + Arithmetic::ArithmeticCalls::rem(Arithmetic::remCall { a: U256::from(23u32), b: U256::from(5u32) }) + .abi_encode(), + ) + .build_and_unwrap_result(); + assert_eq!( + U256::from(3u32), + U256::from_be_bytes::<32>(result.data.try_into().unwrap()), + "REM(23, 5) should equal 3 for {:?}", fixture_type + ); + } + + { + // Test large numbers with positive divisor + let large_a = U256::from(i64::MAX as u64); + let large_b = U256::from(1000u32); + let expected = large_a % large_b; + let result = builder::bare_call(addr) + .data( + Arithmetic::ArithmeticCalls::rem(Arithmetic::remCall { a: large_a, b: large_b }) + .abi_encode(), + ) + .build_and_unwrap_result(); + assert_eq!( + expected, + U256::from_be_bytes::<32>(result.data.try_into().unwrap()), + "REM({}, {}) should equal {} for {:?}", large_a, large_b, expected, fixture_type + ); + } + }); + } +} + +#[test] +fn smod_works() { + for fixture_type in [FixtureType::Solc, FixtureType::Resolc] { + let (code, _) = compile_module_with_type("Arithmetic", fixture_type).unwrap(); + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + { + let result = builder::bare_call(addr) + .data( + Arithmetic::ArithmeticCalls::smod(Arithmetic::smodCall { a: I256::from_raw(U256::from(20u32)), b: I256::from_raw(U256::from(5u32)) }) + .abi_encode(), + ) + .build_and_unwrap_result(); + assert_eq!( + I256::from_raw(U256::from(0u32)), + I256::from_be_bytes::<32>(result.data.try_into().unwrap()), + "SMOD(20, 5) should equal 0 for {:?}", fixture_type + ); + } + + { + // Test with remainder: 23 % 5 = 3 + let result = builder::bare_call(addr) + .data( + Arithmetic::ArithmeticCalls::smod(Arithmetic::smodCall { a: I256::from_raw(U256::from(23u32)), b: I256::from_raw(U256::from(5u32)) }) + .abi_encode(), + ) + .build_and_unwrap_result(); + assert_eq!( + I256::from_raw(U256::from(3u32)), + I256::from_be_bytes::<32>(result.data.try_into().unwrap()), + "SMOD(23, 5) should equal 3 for {:?}", fixture_type + ); + } + + { + // Test large numbers with positive divisor + let large_a = I256::from_raw(U256::from(i64::MAX as u64)); + let large_b = I256::from_raw(U256::from(1000u32)); + let expected = large_a % large_b; + let result = builder::bare_call(addr) + .data( + Arithmetic::ArithmeticCalls::smod(Arithmetic::smodCall { a: large_a, b: large_b }) + .abi_encode(), + ) + .build_and_unwrap_result(); + assert_eq!( + expected, + I256::from_be_bytes::<32>(result.data.try_into().unwrap()), + "SMOD({}, {}) should equal {} for {:?}", large_a, large_b, expected, fixture_type + ); + } + + { + // Test negative numbers: -23 % 5 should equal -3 in most implementations + // We need to use two's complement representation for negative numbers + let neg_23 = I256::from_raw(U256::MAX - U256::from(22u32)); // -23 in two's complement + let pos_5 = I256::from_raw(U256::from(5u32)); + let result = builder::bare_call(addr) + .data( + Arithmetic::ArithmeticCalls::smod(Arithmetic::smodCall { a: neg_23, b: pos_5 }) + .abi_encode(), + ) + .build_and_unwrap_result(); + let neg_3 = I256::from_raw(U256::MAX - U256::from(2u32)); // -3 in two's complement + assert_eq!( + neg_3, + I256::from_be_bytes::<32>(result.data.try_into().unwrap()), + "REM(-23, 5) should equal -3 for {:?}", fixture_type + ); + } + }); + } +} + +#[test] +fn umod_works() { + for fixture_type in [FixtureType::Solc, FixtureType::Resolc] { + let (code, _) = compile_module_with_type("Arithmetic", fixture_type).unwrap(); + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + { + let result = builder::bare_call(addr) + .data( + Arithmetic::ArithmeticCalls::umod(Arithmetic::umodCall { a: U256::from(23u32), b: U256::from(5u32) }) + .abi_encode(), + ) + .build_and_unwrap_result(); + assert_eq!( + U256::from(3u32), + U256::from_be_bytes::<32>(result.data.try_into().unwrap()), + "UMOD(23, 5) should equal 3 for {:?}", fixture_type + ); + } + }); + } +} + +#[test] +fn addmod_works() { + for fixture_type in [FixtureType::Solc, FixtureType::Resolc] { + let (code, _) = compile_module_with_type("Arithmetic", fixture_type).unwrap(); + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + { + // Test ADDMOD: (10 + 15) % 7 = 25 % 7 = 4 + let result = builder::bare_call(addr) + .data( + Arithmetic::ArithmeticCalls::addmod(Arithmetic::addmodCall { + a: U256::from(10u32), + b: U256::from(15u32), + n: U256::from(7u32) + }) + .abi_encode(), + ) + .build_and_unwrap_result(); + assert_eq!( + U256::from(4u32), + U256::from_be_bytes::<32>(result.data.try_into().unwrap()), + "ADDMOD(10, 15, 7) should equal 4 for {:?}", fixture_type + ); + } + }); + } +} + +#[test] +fn mulmod_works() { + for fixture_type in [FixtureType::Solc, FixtureType::Resolc] { + let (code, _) = compile_module_with_type("Arithmetic", fixture_type).unwrap(); + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + { + // Test MULMOD: (6 * 7) % 10 = 42 % 10 = 2 + let result = builder::bare_call(addr) + .data( + Arithmetic::ArithmeticCalls::mulmod(Arithmetic::mulmodCall { + a: U256::from(6u32), + b: U256::from(7u32), + n: U256::from(10u32) + }) + .abi_encode(), + ) + .build_and_unwrap_result(); + assert_eq!( + U256::from(2u32), + U256::from_be_bytes::<32>(result.data.try_into().unwrap()), + "MULMOD(6, 7, 10) should equal 2 for {:?}", fixture_type + ); + } + }); + } +} + +#[test] +fn exp_works() { + for fixture_type in [FixtureType::Solc, FixtureType::Resolc] { + let (code, _) = compile_module_with_type("Arithmetic", fixture_type).unwrap(); + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + { + // Test EXP: 2 ** 3 = 8 + let result = builder::bare_call(addr) + .data( + Arithmetic::ArithmeticCalls::exp(Arithmetic::expCall { + a: U256::from(2u32), + b: U256::from(3u32) + }) + .abi_encode(), + ) + .build_and_unwrap_result(); + assert_eq!( + U256::from(8u32), + U256::from_be_bytes::<32>(result.data.try_into().unwrap()), + "EXP(2, 3) should equal 8 for {:?}", fixture_type + ); + } + + { + // Test EXP: 5 ** 2 = 25 + let result = builder::bare_call(addr) + .data( + Arithmetic::ArithmeticCalls::exp(Arithmetic::expCall { + a: U256::from(5u32), + b: U256::from(2u32) + }) + .abi_encode(), + ) + .build_and_unwrap_result(); + assert_eq!( + U256::from(25u32), + U256::from_be_bytes::<32>(result.data.try_into().unwrap()), + "EXP(5, 2) should equal 25 for {:?}", fixture_type + ); + } + + { + // Test EXP: 10 ** 0 = 1 (anything to power 0 is 1) + let result = builder::bare_call(addr) + .data( + Arithmetic::ArithmeticCalls::exp(Arithmetic::expCall { + a: U256::from(10u32), + b: U256::from(0u32) + }) + .abi_encode(), + ) + .build_and_unwrap_result(); + assert_eq!( + U256::from(1u32), + U256::from_be_bytes::<32>(result.data.try_into().unwrap()), + "EXP(10, 0) should equal 1 for {:?}", fixture_type + ); + } + + { + // Test EXP: 1 ** 100 = 1 (1 to any power is 1) + let result = builder::bare_call(addr) + .data( + Arithmetic::ArithmeticCalls::exp(Arithmetic::expCall { + a: U256::from(1u32), + b: U256::from(100u32) + }) + .abi_encode(), + ) + .build_and_unwrap_result(); + assert_eq!( + U256::from(1u32), + U256::from_be_bytes::<32>(result.data.try_into().unwrap()), + "EXP(1, 100) should equal 1 for {:?}", fixture_type + ); + } + + { + // Test EXP with larger numbers: 3 ** 4 = 81 + let result = builder::bare_call(addr) + .data( + Arithmetic::ArithmeticCalls::exp(Arithmetic::expCall { + a: U256::from(3u32), + b: U256::from(4u32) + }) + .abi_encode(), + ) + .build_and_unwrap_result(); + assert_eq!( + U256::from(81u32), + U256::from_be_bytes::<32>(result.data.try_into().unwrap()), + "EXP(3, 4) should equal 81 for {:?}", fixture_type + ); + } + }); + } +} + +#[test] +fn signextend_works() { + for fixture_type in [FixtureType::Solc, FixtureType::Resolc] { + let (code, _) = compile_module_with_type("Arithmetic", fixture_type).unwrap(); + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + { + // Test SIGNEXTEND: extend 8-bit signed value 0xFF (-1) to 256 bits + // signextend(0, 0xFF) should extend from 8 bits, result should be all 1s (U256::MAX) + let result = builder::bare_call(addr) + .data( + Arithmetic::ArithmeticCalls::signextend(Arithmetic::signextendCall { + i: U256::from(0u32), // extend from byte 0 (8 bits) + x: U256::from(0xFFu32) // value 0xFF (all 1s in 8 bits) + }) + .abi_encode(), + ) + .build_and_unwrap_result(); + assert_eq!( + U256::MAX, // Should be all 1s when sign-extending 0xFF from 8 bits + U256::from_be_bytes::<32>(result.data.try_into().unwrap()), + "SIGNEXTEND(0, 0xFF) should equal U256::MAX for {:?}", fixture_type + ); + } + + { + // Test SIGNEXTEND: extend 8-bit positive value 0x7F to 256 bits + // signextend(0, 0x7F) should keep it positive (sign bit is 0) + let result = builder::bare_call(addr) + .data( + Arithmetic::ArithmeticCalls::signextend(Arithmetic::signextendCall { + i: U256::from(0u32), // extend from byte 0 (8 bits) + x: U256::from(0x7Fu32) // value 0x7F (positive in 8 bits) + }) + .abi_encode(), + ) + .build_and_unwrap_result(); + assert_eq!( + U256::from(0x7Fu32), // Should remain 0x7F (positive) + U256::from_be_bytes::<32>(result.data.try_into().unwrap()), + "SIGNEXTEND(0, 0x7F) should equal 0x7F for {:?}", fixture_type + ); + } + + { + // Test SIGNEXTEND: extend 16-bit signed value 0x8000 (-32768) to 256 bits + // signextend(1, 0x8000) should extend from 16 bits with sign bit set + let result = builder::bare_call(addr) + .data( + Arithmetic::ArithmeticCalls::signextend(Arithmetic::signextendCall { + i: U256::from(1u32), // extend from byte 1 (16 bits) + x: U256::from(0x8000u32) // value 0x8000 (negative in 16 bits) + }) + .abi_encode(), + ) + .build_and_unwrap_result(); + // 0x8000 in 16 bits is negative, so should become 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_8000 + let expected = U256::MAX - U256::from(0x7FFFu32); // Two's complement representation + assert_eq!( + expected, + U256::from_be_bytes::<32>(result.data.try_into().unwrap()), + "SIGNEXTEND(1, 0x8000) should sign-extend negative 16-bit value for {:?}", fixture_type + ); + } + + { + // Test SIGNEXTEND: extend 16-bit positive value 0x7FFF to 256 bits + // signextend(1, 0x7FFF) should keep it positive + let result = builder::bare_call(addr) + .data( + Arithmetic::ArithmeticCalls::signextend(Arithmetic::signextendCall { + i: U256::from(1u32), // extend from byte 1 (16 bits) + x: U256::from(0x7FFFu32) // value 0x7FFF (positive in 16 bits) + }) + .abi_encode(), + ) + .build_and_unwrap_result(); + assert_eq!( + U256::from(0x7FFFu32), // Should remain 0x7FFF (positive) + U256::from_be_bytes::<32>(result.data.try_into().unwrap()), + "SIGNEXTEND(1, 0x7FFF) should equal 0x7FFF for {:?}", fixture_type + ); + } + + { + // Test SIGNEXTEND: i >= 32 should return original value unchanged + // signextend(32, value) should return value as-is + let test_value = U256::from(0x123456789ABCDEFu64); + let result = builder::bare_call(addr) + .data( + Arithmetic::ArithmeticCalls::signextend(Arithmetic::signextendCall { + i: U256::from(32u32), // >= 32, should not modify + x: test_value + }) + .abi_encode(), + ) + .build_and_unwrap_result(); + assert_eq!( + test_value, + U256::from_be_bytes::<32>(result.data.try_into().unwrap()), + "SIGNEXTEND(32, value) should return value unchanged for {:?}", fixture_type + ); + } + }); + } +} \ No newline at end of file diff --git a/substrate/frame/revive/src/tests/sol/block_info.rs b/substrate/frame/revive/src/tests/sol/block_info.rs index e8ea76c30c9b8..e688b7b96f232 100644 --- a/substrate/frame/revive/src/tests/sol/block_info.rs +++ b/substrate/frame/revive/src/tests/sol/block_info.rs @@ -18,7 +18,7 @@ //! The pallet-revive shared VM integration test suite. use crate::{ - test_utils::{builder::Contract, ALICE}, + test_utils::{builder::Contract, ALICE, EVE_ADDR}, tests::{builder, ExtBuilder, System, Test}, Code, Config, }; @@ -53,3 +53,41 @@ fn block_number_works() { }); } } + +/// Tests that the coinbase opcode works as expected. +#[test] +fn coinbase_works() { + let eve_as_u256 = { + let mut bytes = [0u8; 32]; + bytes[12..32].copy_from_slice(&EVE_ADDR.0); + U256::from_be_bytes(bytes) + }; + for fixture_type in [FixtureType::Solc, FixtureType::Resolc] { + let (code, _) = compile_module_with_type("BlockInfo", fixture_type).unwrap(); + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + let result = builder::bare_call(addr) + .data( + BlockInfo::BlockInfoCalls::coinbase(BlockInfo::coinbaseCall {}) + .abi_encode(), + ) + .build_and_unwrap_result(); + + // Verify that we got a 32-byte result (address is padded to 32 bytes in EVM) + assert_eq!(result.data.len(), 32, "Coinbase should return a 32-byte padded address"); + + // The coinbase opcode should return the current block's beneficiary address + let coinbase_result = U256::from_be_bytes::<32>(result.data.try_into().unwrap()); + + assert_eq!( + coinbase_result, + eve_as_u256, + "Coinbase should return expected beneficiary address for {:?}", + fixture_type + ); + }); + } +} diff --git a/substrate/frame/revive/src/vm/evm/instructions/arithmetic.rs b/substrate/frame/revive/src/vm/evm/instructions/arithmetic.rs index 6aa47e2bae86b..67910ad1d3581 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/arithmetic.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/arithmetic.rs @@ -19,7 +19,7 @@ use super::{ i256::{i256_div, i256_mod}, Context, }; -use crate::vm::Ext; +use crate::{vm::Ext, RuntimeCosts}; use revm::{ interpreter::{ gas as revm_gas, @@ -30,7 +30,7 @@ use revm::{ /// Implements the ADD instruction - adds two values from stack. pub fn add<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::VERYLOW); + gas!(context.interpreter, RuntimeCosts::EVMGas(3)); popn_top!([op1], op2, context.interpreter); *op2 = op1.wrapping_add(*op2); } diff --git a/substrate/frame/revive/src/vm/evm/instructions/block_info.rs b/substrate/frame/revive/src/vm/evm/instructions/block_info.rs index 375de2f6f9c25..3d4877439e0a1 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/block_info.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/block_info.rs @@ -19,7 +19,10 @@ use super::Context; use crate::{vm::Ext, RuntimeCosts}; use revm::{ interpreter::{gas as revm_gas, host::Host, interpreter_types::RuntimeFlag}, - primitives::{hardfork::SpecId::*, U256}, + primitives::{hardfork::SpecId::*, U256, Address, }, +}; +use crate::{ + evm::H160, }; /// EIP-1344: ChainID opcode @@ -33,8 +36,9 @@ pub fn chainid<'ext, E: Ext>(context: Context<'_, 'ext, E>) { /// /// Pushes the current block's beneficiary address onto the stack. pub fn coinbase<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::BASE); - push!(context.interpreter, context.host.beneficiary().into_word().into()); + gas!(context.interpreter, RuntimeCosts::BlockAuthor); + let coinbase: Address = context.interpreter.extend.block_author().unwrap_or(H160::zero()).0.into(); + push!(context.interpreter, coinbase.into_word().into()); } /// Implements the TIMESTAMP instruction.