diff --git a/crates/library/std/src/abi.fe b/crates/library/std/src/abi.fe new file mode 100644 index 0000000000..084a55cd9f --- /dev/null +++ b/crates/library/std/src/abi.fe @@ -0,0 +1,223 @@ +use ingot::evm +use ingot::context::CalldataReader +use ingot::traits::Max +use ingot::buf::{MemoryBuffer, MemoryBufferReader, MemoryBufferWriter} + +pub trait AbiDecode { + fn decode(mut reader: CalldataReader) -> Self; +} + +pub trait AbiEncode { + fn encode(self, mut writer: MemoryBufferWriter); +} + + +// unsigned integers + +impl AbiDecode for u256 { + fn decode(mut reader: CalldataReader) -> Self { + return reader.read_u256() + } +} + +impl AbiEncode for u256 { + fn encode(self, mut writer: MemoryBufferWriter) { + return writer.write(value: self) + } +} + +impl AbiDecode for u128 { + fn decode(mut reader: CalldataReader) -> Self { + let value: u256 = reader.read_u256() + if value > u128::max() { + revert + } else { + return u128(value) + } + } +} + +impl AbiEncode for u128 { + fn encode(self, mut writer: MemoryBufferWriter) { + return writer.write(value: u256(self)) + } +} + +impl AbiDecode for u64 { + fn decode(mut reader: CalldataReader) -> Self { + let value: u256 = reader.read_u256() + if value > u64::max() { + revert + } else { + return u64(value) + } + } +} + +impl AbiEncode for u64 { + fn encode(self, mut writer: MemoryBufferWriter) { + return writer.write(value: u256(self)) + } +} + +impl AbiDecode for u32 { + fn decode(mut reader: CalldataReader) -> Self { + let value: u256 = reader.read_u256() + if value > u32::max() { + revert + } else { + return u32(value) + } + } +} + +impl AbiEncode for u32 { + fn encode(self, mut writer: MemoryBufferWriter) { + return writer.write(value: u256(self)) + } +} + +impl AbiDecode for u16 { + fn decode(mut reader: CalldataReader) -> Self { + let value: u256 = reader.read_u256() + if value > u16::max() { + revert + } else { + return u16(value) + } + } +} + +impl AbiEncode for u16 { + fn encode(self, mut writer: MemoryBufferWriter) { + return writer.write(value: u256(self)) + } +} + +impl AbiDecode for u8 { + fn decode(mut reader: CalldataReader) -> Self { + let value: u256 = reader.read_u256() + if value > u8::max() { + revert + } else { + return u8(value) + } + } +} + +impl AbiEncode for u8 { + fn encode(self, mut writer: MemoryBufferWriter) { + return writer.write(value: u256(self)) + } +} + +// signed integers + +// todo + +// misc types + +impl AbiDecode for bool { + fn decode(mut reader: CalldataReader) -> Self { + let value: u256 = reader.read_u256() + + if value == 0 { + return false + } else if value == 1 { + return true + } else { + revert + } + } +} + +impl AbiEncode for bool { + fn encode(self, mut writer: MemoryBufferWriter) { + if self { + writer.write(value: 1) + } else { + writer.write(value: 0) + } + } +} + +impl AbiDecode for address { + fn decode(mut reader: CalldataReader) -> Self { + let value: u256 = reader.read_u256() + + if value > 2 ^ 160 { + revert + } else { + return address(value) + } + } +} + +impl AbiEncode for address { + fn encode(self, mut writer: MemoryBufferWriter) { + writer.write(value: u256(self)) + } +} + +// // generic impl of array encoding +// impl AbiEncode for Array +// where +// T: AbiEncode +// { +// fn encode(self, mut writer: MemoryBufferWriter) { +// let i: usize = 0 + +// while i < N { +// self[i].encode(buf) +// i += 1 +// } +// } +// } + +// // generic impl of array decoding +// impl AbiDecode for Array +// where +// T: AbiDecode +// { +// fn decode(mut reader: CalldataReader) -> Self { +// let decoded_array: Self +// let i: usize = 0 + +// while i < N { +// decoded_array[i] = T::decode(buf) +// i += 1 +// } + +// return decoded_array +// } +// } + +// // tuple encode derivation +// impl AbiEncode for (T1, T2, ...) +// where +// T1: AbiEncode +// T2: AbiEncode +// ... +// { +// fn encode(self, mut writer: MemoryBufferWriter) { +// self.item0.encode(writer) +// self.item1.encode(writer) +// ... +// } +// } + +// // tuple decode derivation +// impl AbiDecode for (T1, T2, ...) +// where +// T1: AbiDecode +// T1: AbiDecode +// ... +// { +// fn decode(mut reader: CalldataReader) -> Self { +// return ( +// u256::decode(reader), +// u256::decode(reader) +// ... +// ) +// } +// } \ No newline at end of file diff --git a/crates/library/std/src/buf.fe b/crates/library/std/src/buf.fe index a1d97af4e6..3363a6c8fb 100644 --- a/crates/library/std/src/buf.fe +++ b/crates/library/std/src/buf.fe @@ -297,3 +297,48 @@ pub struct RawCallBuffer { return self.buf.writer() } } + +pub struct CalldataReader { + cur: Cursor + + fn read_offset(mut self, len: u256) -> u256 { + return self.cur.advance(len) + } + + fn read_n(mut self, len: u256) -> u256 { + let offset: u256 = self.read_offset(len) + unsafe { + let value: u256 = evm::call_data_load(offset) + return evm::shr(bits: 256 - len * 8, value) + } + } + + pub fn read_u8(mut self) -> u8 { + return u8(self.read_n(len: 1)) + } + + pub fn read_u16(mut self) -> u16 { + return u16(self.read_n(len: 2)) + } + + pub fn read_u32(mut self) -> u32 { + return u32(self.read_n(len: 4)) + } + + pub fn read_u64(mut self) -> u64 { + return u64(self.read_n(len: 8)) + } + + pub fn read_u128(mut self) -> u128 { + return u128(self.read_n(len: 16)) + } + + pub fn read_u256(mut self) -> u256 { + let offset: u256 = self.read_offset(len: 32) + unsafe { + let value: u256 = evm::mload(offset) + return value + } + } + +} \ No newline at end of file diff --git a/crates/library/std/src/context.fe b/crates/library/std/src/context.fe index 8a7dd10026..8030c2d878 100644 --- a/crates/library/std/src/context.fe +++ b/crates/library/std/src/context.fe @@ -19,6 +19,46 @@ pub trait Emittable { fn emit(self, _ val: OutOfReachMarker); } +pub struct CalldataReader { + cur_offset: u256 + len: u256 + + pub unsafe fn new(len: u256) -> CalldataReader { + return CalldataReader(cur_offset: 0, len) + } + + pub fn remainder(self) -> u256 { + return self.len - self.cur_offset + } + + pub fn read_u256(mut self) -> u256 { + unsafe { + let value: u256 = evm::call_data_load(offset: self.cur_offset) + self.cur_offset += 32 + assert self.cur_offset <= self.len + return value + } + } + + pub fn read_u16(mut self) -> u16 { + unsafe { + let value: u256 = evm::call_data_load(offset: self.cur_offset) + self.cur_offset += 2 + assert self.cur_offset <= self.len + return u16(evm::shr(bits: 240, value)) + } + } + + pub fn read_u32(mut self) -> u32 { + unsafe { + let value: u256 = evm::call_data_load(offset: self.cur_offset) + self.cur_offset += 4 + assert self.cur_offset <= self.len + return u32(evm::shr(bits: 224, value)) + } + } +} + pub struct Context { pub fn base_fee(self) -> u256 { unsafe { return evm::base_fee() } @@ -107,6 +147,13 @@ pub struct Context { } } + pub fn calldata_reader(self) -> CalldataReader { + unsafe { + let len: u256 = evm::call_data_size() + return CalldataReader::new(len) + } + } + pub fn emit(mut self, _ val: T) { val.emit(OutOfReachMarker()) } diff --git a/crates/tests/fixtures/files/abi.fe b/crates/tests/fixtures/files/abi.fe new file mode 100644 index 0000000000..5d3d3ec019 --- /dev/null +++ b/crates/tests/fixtures/files/abi.fe @@ -0,0 +1,209 @@ +use std::abi::{AbiDecode, AbiEncode} +use std::context::{Context, CalldataReader} +use std::buf::{ + MemoryBuffer, + MemoryBufferWriter, + MemoryBufferReader, + RawCallBuffer +} +use std::evm +use std::traits::Max + +contract AbiDecodeU256 { + pub unsafe fn __call__(ctx: Context) { + let mut reader: CalldataReader = ctx.calldata_reader() + let value: u256 = u256::decode(reader) + + let mut buf: MemoryBuffer = MemoryBuffer::new(len: 32) + let mut writer: MemoryBufferWriter = buf.writer() + value.encode(writer) + + evm::return_mem(buf) + } +} + +contract AbiDecodeU128 { + pub unsafe fn __call__(ctx: Context) { + let mut reader: CalldataReader = ctx.calldata_reader() + let value: u128 = u128::decode(reader) + + let mut buf: MemoryBuffer = MemoryBuffer::new(len: 32) + let mut writer: MemoryBufferWriter = buf.writer() + value.encode(writer) + + evm::return_mem(buf) + } +} + +contract AbiDecodeAddress { + pub unsafe fn __call__(ctx: Context) { + let mut reader: CalldataReader = ctx.calldata_reader() + let value: address = address::decode(reader) + + let mut buf: MemoryBuffer = MemoryBuffer::new(len: 32) + let mut writer: MemoryBufferWriter = buf.writer() + value.encode(writer) + + evm::return_mem(buf) + } +} + +contract AbiDecodeBool { + pub unsafe fn __call__(ctx: Context) { + let mut reader: CalldataReader = ctx.calldata_reader() + let value: bool = bool::decode(reader) + + let mut buf: MemoryBuffer = MemoryBuffer::new(len: 32) + let mut writer: MemoryBufferWriter = buf.writer() + value.encode(writer) + + evm::return_mem(buf) + } +} + +#test +fn u256_abi(mut ctx: Context) { + let decoder: address = address(AbiDecodeU256.create(ctx, 0)) + let mut buf: RawCallBuffer = RawCallBuffer::new( + input_len: 32, + output_len: 32 + ) + + let mut writer: MemoryBufferWriter = buf.writer() + writer.write(value: 42) + + ctx.raw_call(addr: decoder, value: 0, buf) + + let mut reader: MemoryBufferReader = buf.reader() + assert reader.read_u256() == 42 +} + +#test +fn u128_abi(mut ctx: Context) { + let decoder: address = address(AbiDecodeU128.create(ctx, 0)) + let mut buf: RawCallBuffer = RawCallBuffer::new( + input_len: 32, + output_len: 32 + ) + + let mut writer: MemoryBufferWriter = buf.writer() + writer.write(value: 42) + + assert ctx.raw_call(addr: decoder, value: 0, buf) + + let mut reader: MemoryBufferReader = buf.reader() + assert reader.read_u256() == 42 +} + +#test +fn u128_abi_invalid(mut ctx: Context) { + let decoder: address = address(AbiDecodeU128.create(ctx, 0)) + let mut buf: RawCallBuffer = RawCallBuffer::new( + input_len: 32, + output_len: 32 + ) + + let mut writer: MemoryBufferWriter = buf.writer() + writer.write(value: u256(u128::max()) + 1) + + assert not ctx.raw_call(addr: decoder, value: 0, buf) +} + +#test +fn address_abi(mut ctx: Context) { + let decoder: address = address(AbiDecodeAddress.create(ctx, 0)) + let mut buf: RawCallBuffer = RawCallBuffer::new( + input_len: 32, + output_len: 32 + ) + + let mut writer: MemoryBufferWriter = buf.writer() + writer.write(value: 42) + + assert ctx.raw_call(addr: decoder, value: 0, buf) + + let mut reader: MemoryBufferReader = buf.reader() + assert reader.read_u256() == 42 +} + +#test +fn address_abi_invalid(mut ctx: Context) { + let decoder: address = address(AbiDecodeAddress.create(ctx, 0)) + let mut buf: RawCallBuffer = RawCallBuffer::new( + input_len: 32, + output_len: 32 + ) + + let mut writer: MemoryBufferWriter = buf.writer() + writer.write(value: u256::max()) + + assert not ctx.raw_call(addr: decoder, value: 0, buf) +} + +#test +fn bool_abi(mut ctx: Context) { + let decoder: address = address(AbiDecodeBool.create(ctx, 0)) + let mut buf: RawCallBuffer = RawCallBuffer::new( + input_len: 32, + output_len: 32 + ) + + let mut writer: MemoryBufferWriter = buf.writer() + writer.write(value: 1) + + assert ctx.raw_call(addr: decoder, value: 0, buf) + + let mut reader: MemoryBufferReader = buf.reader() + assert reader.read_u256() == 1 +} + +#test +fn bool_abi_invalid(mut ctx: Context) { + let decoder: address = address(AbiDecodeBool.create(ctx, 0)) + let mut buf: RawCallBuffer = RawCallBuffer::new( + input_len: 32, + output_len: 32 + ) + + let mut writer: MemoryBufferWriter = buf.writer() + writer.write(value: 3) + + assert not ctx.raw_call(addr: decoder, value: 0, buf) +} + +// type MyArray = Array +// type MyTuple = (u256, u256) + +// #test +// unsafe fn array_decode() { +// evm::mstore(offset: HASH_SCRATCH_SPACE_START, value: 26) +// evm::mstore(offset: HASH_SCRATCH_SPACE_START + 32, value: 42) + +// let mut buf: MemoryBuffer = MemoryBuffer::new( +// start: HASH_SCRATCH_SPACE_START, +// end: HASH_SCRATCH_SPACE_START + HASH_SCRATCH_SPACE_SIZE +// ) + +// // parser panics +// // let my_array: = Array::decode(buf) +// let my_array: MyArray = MyArray::decode(buf) +// assert my_array[0] == 26 +// assert my_array[1] == 42 +// } + +// #test +// unsafe fn tuple_decode() { +// evm::mstore(offset: HASH_SCRATCH_SPACE_START, value: 26) +// evm::mstore(offset: HASH_SCRATCH_SPACE_START + 32, value: 42) + +// let mut buf: MemoryBuffer = MemoryBuffer::new( +// start: HASH_SCRATCH_SPACE_START, +// end: HASH_SCRATCH_SPACE_START + HASH_SCRATCH_SPACE_SIZE +// ) + +// // parser panics +// // let my_tuple: (u256, u256) = (u256, u256)::decode(buf) +// let my_tuple: MyTuple = MyTuple::decode(buf) +// assert my_tuple.item0 == 26 +// assert my_tuple.item1 == 42 +// } diff --git a/crates/tests/fixtures/files/contract_abi.fe b/crates/tests/fixtures/files/contract_abi.fe new file mode 100644 index 0000000000..b1d7e1cab1 --- /dev/null +++ b/crates/tests/fixtures/files/contract_abi.fe @@ -0,0 +1,50 @@ +use std::buf::{MemoryBuffer, MemoryBufferReader, MemoryBufferWriter} +use std::abi::{AbiEncode, AbiDecode} +use std::context::CalldataReader +use std::evm + +struct UnknownSelector { + pub selector: u256 +} + +contract MyContractInterface { + pub fn set_foo(mut self, foo: u256) { + revert UnknownSelector(selector: 0) + } + + pub fn get_foo(self) -> u256 { + revert UnknownSelector(selector: 0) + } +} + +contract MyContract { + foo: u256 + + pub unsafe fn __call__(mut self, ctx: Context) { + let mut reader: CalldataReader = ctx.calldata_reader() + + let selector: u32 = reader.read_u32() + + if selector == 0xe5d5dfbc { + self.foo = u256::decode(reader) + } else if selector == 0x62e2d0de { + let mut buf: MemoryBuffer = MemoryBuffer::new(len: 32) + let mut writer: MemoryBufferWriter = buf.writer() + // self.foo.encode(writer) + let value: u256 = self.foo + value.encode(writer) + evm::return_mem(buf) + } else { + revert UnknownSelector(selector) + } + } +} + +#test +unsafe fn contract_call() { + let mut ctx: Context = Context() + let mut my_contract: MyContractInterface = MyContractInterface(address(MyContract.create(ctx, 0))) + assert my_contract.get_foo() == 0 + my_contract.set_foo(foo: 42) + assert my_contract.get_foo() == 42 +} \ No newline at end of file diff --git a/notes b/notes new file mode 100644 index 0000000000..bda5674dfc --- /dev/null +++ b/notes @@ -0,0 +1,2 @@ +- update vs code syntax (comments issue) +- ctx gas remaining