diff --git a/Makefile b/Makefile index 84f31a2..083335f 100644 --- a/Makefile +++ b/Makefile @@ -52,7 +52,16 @@ MX_RUST_TWO_CONTRACTS_TESTING_OUTPUT_DIR ::= .build/mx-rust-two-contracts/output MX_RUST_TWO_CONTRACTS_TESTING_INPUTS ::= $(wildcard $(MX_RUST_TWO_CONTRACTS_TESTING_INPUT_DIR)/*.run) MX_RUST_TWO_CONTRACTS_TESTING_OUTPUTS ::= $(patsubst $(MX_RUST_TWO_CONTRACTS_TESTING_INPUT_DIR)/%,$(MX_RUST_TWO_CONTRACTS_TESTING_OUTPUT_DIR)/%.executed.kore,$(MX_RUST_TWO_CONTRACTS_TESTING_INPUTS)) -.PHONY: clean build test syntax-test preprocessing-test execution-test mx-test mx-rust-test mx-rust-contract-test mx-rust-two-contracts-test +DEMOS_TESTING_KOMPILED ::= $(MX_RUST_CONTRACT_TESTING_KOMPILED) +DEMOS_TESTING_TIMESTAMP ::= $(DEMOS_TESTING_KOMPILED)/timestamp +DEMOS_TESTING_INPUT_DIR ::= tests/demos +DEMOS_TESTING_OUTPUT_DIR ::= .build/demos/output +DEMOS_TESTING_INPUTS ::= $(wildcard $(DEMOS_TESTING_INPUT_DIR)/*.run) +DEMOS_TESTING_OUTPUTS ::= $(patsubst $(DEMOS_TESTING_INPUT_DIR)/%,$(DEMOS_TESTING_OUTPUT_DIR)/%.executed.kore,$(DEMOS_TESTING_INPUTS)) + +.PHONY: clean build test syntax-test preprocessing-test execution-test mx-test mx-rust-test mx-rust-contract-test mx-rust-two-contracts-test demos-test + +all: build test clean: rm -r .build @@ -65,7 +74,7 @@ build: $(RUST_PREPROCESSING_TIMESTAMP) \ $(MX_RUST_CONTRACT_TESTING_TIMESTAMP) \ $(MX_RUST_TWO_CONTRACTS_TESTING_TIMESTAMP) -test: build syntax-test preprocessing-test execution-test mx-test mx-rust-test mx-rust-contract-test mx-rust-two-contracts-test +test: build syntax-test preprocessing-test execution-test mx-test mx-rust-test mx-rust-contract-test mx-rust-two-contracts-test demos-test syntax-test: $(SYNTAX_OUTPUTS) @@ -81,6 +90,8 @@ mx-rust-contract-test: $(MX_RUST_CONTRACT_TESTING_OUTPUTS) mx-rust-two-contracts-test: $(MX_RUST_TWO_CONTRACTS_TESTING_OUTPUTS) +demos-test: $(DEMOS_TESTING_OUTPUTS) + $(RUST_PREPROCESSING_TIMESTAMP): $(RUST_SEMANTICS_FILES) # Workaround for https://github.com/runtimeverification/k/issues/4141 -rm -r $(RUST_PREPROCESSING_KOMPILED) @@ -242,3 +253,27 @@ $(MX_RUST_TWO_CONTRACTS_TESTING_OUTPUT_DIR)/%.run.executed.kore: \ -pARGS2=$(CURDIR)/parsers/args-mx-rust-two-contracts.sh cat $@.tmp | grep -q "Lbl'-LT-'k'-GT-'{}(dotk{}())" mv -f $@.tmp $@ + + +# TODO: Add $(shell echo "$<" | sed 's/\.[^.]*.run$$//').rs +# as a dependency +$(DEMOS_TESTING_OUTPUT_DIR)/%.run.executed.kore: \ + $(DEMOS_TESTING_INPUT_DIR)/%.run \ + $(DEMOS_TESTING_TIMESTAMP) \ + $(wildcard parsers/inc-*.sh) \ + parsers/args-mx-rust-contract.sh \ + parsers/contract-mx-rust-contract.sh \ + parsers/test-mx-rust-contract.sh + mkdir -p $(DEMOS_TESTING_OUTPUT_DIR) + krun \ + "$(shell echo "$<" | sed 's/\.[^.]*.run$$//').rs" \ + --definition $(DEMOS_TESTING_KOMPILED) \ + --parser $(CURDIR)/parsers/contract-mx-rust-contract.sh \ + --output kore \ + --output-file $@.tmp \ + -cTEST='$(shell cat $<)' \ + -pTEST=$(CURDIR)/parsers/test-mx-rust-contract.sh \ + -cARGS='$(shell cat $(patsubst %.run,%.args,$<))' \ + -pARGS=$(CURDIR)/parsers/args-mx-rust-contract.sh + cat $@.tmp | grep -q "Lbl'-LT-'k'-GT-'{}(dotk{}())" + mv -f $@.tmp $@ diff --git a/mx-rust-semantics/main/glue.md b/mx-rust-semantics/main/glue.md index 0b725e1..06fda76 100644 --- a/mx-rust-semantics/main/glue.md +++ b/mx-rust-semantics/main/glue.md @@ -97,6 +97,10 @@ module MX-RUST-GLUE syntax MxRustInstruction ::= cloneValue(Expression) [strict] // TODO: Figure out if we need to do a deeper clone for, e.g., structs rule cloneValue(ptrValue(_, V:Value)) => mxRustNewValue(V) + + rule mxRustGetBuffer(ptrValue(_, i32(BufferId:MInt{32}))) + => mxGetBuffer(MInt2Unsigned(BufferId)) + endmodule ``` diff --git a/mx-rust-semantics/main/modules.md b/mx-rust-semantics/main/modules.md index a4a15c6..2c3d66d 100644 --- a/mx-rust-semantics/main/modules.md +++ b/mx-rust-semantics/main/modules.md @@ -5,6 +5,7 @@ requires "modules/biguint.md" requires "modules/blockchain.md" requires "modules/call-value.md" requires "modules/hooks.md" +requires "modules/managed-buffer.md" requires "modules/managed-vec.md" requires "modules/multi-value-encoded.md" requires "modules/proxy.md" @@ -18,6 +19,7 @@ module MX-RUST-MODULES imports private MX-RUST-MODULES-BLOCKCHAIN imports private MX-RUST-MODULES-CALL-VALUE imports private MX-RUST-MODULES-HOOKS + imports private MX-RUST-MODULES-MANAGED-BUFFER imports private MX-RUST-MODULES-MANAGED-VEC imports private MX-RUST-MODULES-MULTI-VALUE-ENCODED imports private MX-RUST-MODULES-PROXY diff --git a/mx-rust-semantics/main/modules/managed-buffer.md b/mx-rust-semantics/main/modules/managed-buffer.md new file mode 100644 index 0000000..3c1895a --- /dev/null +++ b/mx-rust-semantics/main/modules/managed-buffer.md @@ -0,0 +1,40 @@ +```k + +module MX-RUST-MODULES-MANAGED-BUFFER + imports private MX-RUST-REPRESENTATION + imports private RUST-SHARED-SYNTAX + + // -------------------------------------- + + syntax MxRustType ::= "managedBufferType" [function, total] + rule managedBufferType + => rustStructType + ( #token("ManagedBuffer", "Identifier"):Identifier + , ( mxRustStructField + ( #token("mx_buffer_id", "Identifier"):Identifier + , MxRust#buffer + ) + , .MxRustStructFields + ) + ) + + rule mxValueToRust + ( #token("ManagedBuffer", "Identifier") + , V:MxValue + ) + => mxToRustTyped(managedBufferType, mxListValue(V , .MxValueList)) + + rule rustValueToMx + ( struct + ( #token("ManagedBuffer", "Identifier"):Identifier + , #token("mx_buffer_id", "Identifier"):Identifier |-> VecValueId:Int + .Map + ) + ) + => mxRustGetBuffer(ptr(VecValueId)) + + // -------------------------------------- + +endmodule + +``` diff --git a/mx-rust-semantics/main/modules/multi-value-encoded.md b/mx-rust-semantics/main/modules/multi-value-encoded.md index 5b5171b..d36746b 100644 --- a/mx-rust-semantics/main/modules/multi-value-encoded.md +++ b/mx-rust-semantics/main/modules/multi-value-encoded.md @@ -62,10 +62,6 @@ module MX-RUST-MODULES-MULTI-VALUE-ENCODED ) => mxRustGetBuffer(ptr(VecValueId)) - syntax MxRustInstruction ::= mxRustGetBuffer(Expression) [strict] - rule mxRustGetBuffer(ptrValue(_, i32(BufferId:MInt{32}))) - => mxGetBuffer(MInt2Unsigned(BufferId)) - // -------------------------------------- endmodule diff --git a/mx-rust-semantics/main/preprocessing/methods.md b/mx-rust-semantics/main/preprocessing/methods.md index 127dca2..569f0ef 100644 --- a/mx-rust-semantics/main/preprocessing/methods.md +++ b/mx-rust-semantics/main/preprocessing/methods.md @@ -336,6 +336,8 @@ module MX-RUST-PREPROCESSING-METHODS [owise] rule getMapperValueType(#token("BigUint", "Identifier") #as T:Type) => rustType(T) + rule getMapperValueType(#token("ManagedBuffer", "Identifier") #as T:Type) + => rustType(T) syntax BlockExpression ::= buildProxyMethodBody ( selfName: SelfSort diff --git a/mx-rust-semantics/main/representation.md b/mx-rust-semantics/main/representation.md index 6c3c32c..2bb4231 100644 --- a/mx-rust-semantics/main/representation.md +++ b/mx-rust-semantics/main/representation.md @@ -28,6 +28,7 @@ module MX-RUST-REPRESENTATION | mxRustLoadPtr(Int) | mxRustGetBigIntFromStruct(Value) | mxRustGetStringFromId(Int) + | mxRustGetBuffer(Expression) [strict] | mxRustNewStruct(MxRustStructType, CallParamsList) [strict(2), result(ValueWithPtr)] | "mxRustCheckMxStatus" diff --git a/tests/demos/erc_20_token.1.args b/tests/demos/erc_20_token.1.args new file mode 100644 index 0000000..2566141 --- /dev/null +++ b/tests/demos/erc_20_token.1.args @@ -0,0 +1 @@ +mxListValue(mxStringValue("MyToken")), mxListValue(mxStringValue("MTKN")), mxIntValue(0) \ No newline at end of file diff --git a/tests/demos/erc_20_token.1.run b/tests/demos/erc_20_token.1.run new file mode 100644 index 0000000..3c29788 --- /dev/null +++ b/tests/demos/erc_20_token.1.run @@ -0,0 +1,37 @@ +setCallee("Owner"); + +push mxListValue(); +push mxStringValue("decimals"); +push mxIntValue(0); +push mxTransfersValue(); +push mxIntValue(0); +push mxStringValue("TestContract"); +call 6 MX#managedExecuteOnDestContext; +check_eq mxIntValue(0); + +push_return_value; +check_eq mxIntValue(18); + +push mxListValue(); +push mxStringValue("name"); +push mxIntValue(0); +push mxTransfersValue(); +push mxIntValue(0); +push mxStringValue("TestContract"); +call 6 MX#managedExecuteOnDestContext; +check_eq mxIntValue(0); + +push_return_value; +check_eq mxStringValue("MyToken"); + +push mxListValue(); +push mxStringValue("symbol"); +push mxIntValue(0); +push mxTransfersValue(); +push mxIntValue(0); +push mxStringValue("TestContract"); +call 6 MX#managedExecuteOnDestContext; +check_eq mxIntValue(0); + +push_return_value; +check_eq mxStringValue("MTKN") diff --git a/tests/demos/erc_20_token.rs b/tests/demos/erc_20_token.rs new file mode 100644 index 0000000..8e03d57 --- /dev/null +++ b/tests/demos/erc_20_token.rs @@ -0,0 +1,150 @@ +// This contract is a translation of +// https://github.com/Pi-Squared-Inc/pi2-examples/blob/b63d0a78922874a486be8a0395a627425fb5a052/solidity/src/tokens/SomeToken.sol +// +// The main change is that the contract does not issue specific error objects +// (e.g. ERC20InsufficientBalance), it just calls `require!` with various +// (string) explanations. +// +// Also, the `totalSupply` endpoint is declared implicitely as a view for +// `s_total_supply`. + +#![no_std] + +#[allow(unused_imports)] +use multiversx_sc::imports::*; + +#[multiversx_sc::contract] +pub trait Erc20Token { + #[view(totalSupply)] + #[storage_mapper("total_supply")] + fn s_total_supply(&self) -> SingleValueMapper; + + #[view(getName)] + #[storage_mapper("name")] + fn s_name(&self) -> SingleValueMapper; + + #[view(getSymbol)] + #[storage_mapper("symbol")] + fn s_symbol(&self) -> SingleValueMapper; + + #[view(getBalances)] + #[storage_mapper("balances")] + fn s_balances(&self, address: &ManagedAddress) -> SingleValueMapper; + + #[view(getAllowances)] + #[storage_mapper("allowances")] + fn s_allowances(&self, account: &ManagedAddress, spender: &ManagedAddress) -> SingleValueMapper; + + #[event("Transfer")] + fn transfer_event(&self, #[indexed] from: &ManagedAddress, #[indexed] to: &ManagedAddress, value: &BigUint); + #[event("Approval")] + fn approval_event(&self, #[indexed] owner: &ManagedAddress, #[indexed] spender: &ManagedAddress, value: &BigUint); + + + #[init] + fn init(&self, name: &ManagedBuffer, symbol: &ManagedBuffer, init_supply: &BigUint) { + self.s_name().set_if_empty(name); + self.s_symbol().set_if_empty(symbol); + self._mint(&self.blockchain().get_caller(), init_supply); + } + + #[upgrade] + fn upgrade(&self) {} + + #[view(decimals)] + fn decimals(&self) -> u8 { + return 18; + } + + // Already declared above + // #[view(totalSupply)] + // fn total_supply(&self) -> BigUint { + // return self.s_total_supply().get(); + // } + + #[view(name)] + fn name(&self) -> ManagedBuffer { + return self.s_name().get(); + } + + #[view(symbol)] + fn symbol(&self) -> ManagedBuffer { + return self.s_symbol().get(); + } + + #[view(balanceOf)] + fn balance_of(&self, account: &ManagedAddress) -> BigUint { + self.s_balances(account).get() + } + + #[endpoint(transfer)] + fn transfer(&self, to: &ManagedAddress, value: BigUint) -> bool { + let owner = self.blockchain().get_caller(); + self._transfer(&owner, to, &value); + true + } + + #[view(allowance)] + fn allowance(&self, owner: &ManagedAddress, spender: &ManagedAddress) -> BigUint { + self.s_allowances(owner, spender).get() + } + + #[endpoint(approve)] + fn approve(&self, spender: &ManagedAddress, value: &BigUint) -> bool { + let owner = self.blockchain().get_caller(); + self._approve(&owner, spender, value, true); + true + } + + #[endpoint(transferFrom)] + fn transfer_from(&self, from: &ManagedAddress, to: &ManagedAddress, value: &BigUint) -> bool { + let spender = self.blockchain().get_caller(); + self._spend_allowance(from, &spender, value); + self._transfer(from, to, value); + return true; + } + + fn _transfer(&self, from: &ManagedAddress, to: &ManagedAddress, value: &BigUint) { + require!(!from.is_zero(), "Invalid sender"); + require!(!to.is_zero(), "Invalid receiver"); + self._update(from, to, value); + self.transfer_event(from, to, value); + } + + fn _update(&self, from: &ManagedAddress, to: &ManagedAddress, value: &BigUint) { + if from.is_zero() { + self.s_total_supply().set(self.s_total_supply().get() + value); + } else { + let from_balance = self.s_balances(from).get(); + require!(value <= &from_balance, "Insufficient balance"); + self.s_balances(from).set(self.s_balances(from).get() - value); + }; + + if to.is_zero() { + self.s_total_supply().set(self.s_total_supply().get() - value); + } else { + self.s_balances(to).set(self.s_balances(to).get() + value); + } + } + + fn _mint(&self, account: &ManagedAddress, value: &BigUint) { + require!(!account.is_zero(), "Zero address"); + self._update(&ManagedAddress::zero(), account, value); + } + + fn _approve(&self, owner: &ManagedAddress, spender: &ManagedAddress, value: &BigUint, emit_event: bool) { + require!(!owner.is_zero(), "Invalid approver"); + require!(!spender.is_zero(), "Invalid spender"); + self.s_allowances(owner, spender).set(value); + if emit_event { + self.approval_event(owner, spender, value); + } + } + + fn _spend_allowance(&self, owner: &ManagedAddress, spender: &ManagedAddress, value: &BigUint) { + let current_allowance = self.allowance(owner, spender); + require!(value <= ¤t_allowance, "Insuficient allowance"); + self._approve(owner, spender, &(current_allowance - value), false); + } + +}