From a22df52023c4eb6187ee4c6b0156f28686baa5b3 Mon Sep 17 00:00:00 2001 From: Mikhail Sherstennikov Date: Thu, 10 Apr 2025 15:51:00 +0300 Subject: [PATCH 1/2] Initial version of non-enshrined tokens --- nil/internal/contracts/contract.go | 1 + nil/internal/execution/state.go | 39 ++++---- nil/internal/execution/zerostate.go | 17 ++++ nil/internal/types/address.go | 2 + nil/internal/vm/precompiled.go | 1 + .../rpc/rawapi/internal/local_account.go | 95 ++++++++++++++++++- nil/tests/multitoken/multitoken_test.go | 4 + nil/tests/rpc_suite.go | 1 + nil/tests/timeouts_no_race.go | 4 +- smart-contracts/contracts/IterableMapping.sol | 53 +++++++++++ smart-contracts/contracts/Nil.sol | 25 ++++- smart-contracts/contracts/NilTokenBase.sol | 9 +- smart-contracts/contracts/Relayer.sol | 49 ++++++++++ smart-contracts/contracts/TokenManager.sol | 63 ++++++++++++ 14 files changed, 327 insertions(+), 36 deletions(-) create mode 100644 smart-contracts/contracts/IterableMapping.sol create mode 100644 smart-contracts/contracts/Relayer.sol create mode 100644 smart-contracts/contracts/TokenManager.sol diff --git a/nil/internal/contracts/contract.go b/nil/internal/contracts/contract.go index 4c2efe563..dcd2de78b 100644 --- a/nil/internal/contracts/contract.go +++ b/nil/internal/contracts/contract.go @@ -29,6 +29,7 @@ const ( NameNilConfigAbi = "NilConfigAbi" NameL1BlockInfo = "system/L1BlockInfo" NameGovernance = "system/Governance" + NameTokenManager = "TokenManager" ) var ( diff --git a/nil/internal/execution/state.go b/nil/internal/execution/state.go index 07e718bd8..eed1dbc3f 100644 --- a/nil/internal/execution/state.go +++ b/nil/internal/execution/state.go @@ -29,7 +29,7 @@ import ( ) const ( - TraceBlocksEnabled = false + TraceBlocksEnabled = true ExternalTransactionVerificationMaxGas = types.Gas(100_000) ModeReadOnly = "read-only" @@ -915,28 +915,6 @@ func (es *ExecutionState) AddOutTransaction( txn.MaxPriorityFeePerGas = es.GetInTransaction().MaxPriorityFeePerGas txn.MaxFeePerGas = es.GetInTransaction().MaxFeePerGas - // In case of bounce transaction, we don't debit token from account - // In case of refund transaction, we don't transfer tokens - if !txn.IsBounce() && !txn.IsRefund() { - acc, err := es.GetAccount(txn.From) - if err != nil { - return nil, err - } - for _, token := range txn.Token { - balance := acc.GetTokenBalance(token.Token) - if balance == nil { - balance = &types.Value{} - } - if balance.Cmp(token.Balance) < 0 { - return nil, fmt.Errorf("%w: %s < %s, token %s", - vm.ErrInsufficientBalance, balance, token.Balance, token.Token) - } - if err := es.SubToken(txn.From, token.Token, token.Balance); err != nil { - return nil, err - } - } - } - // Use next TxId txn.TxId = es.OutTxCounts[txn.To.ShardId()] es.OutTxCounts[txn.To.ShardId()] = txn.TxId + 1 @@ -1344,6 +1322,8 @@ func (es *ExecutionState) handleExecutionTransaction( } defer es.resetVm() + //es.EnableVmTracing() + es.preTxHookCall(transaction) defer func() { es.postTxHookCall(transaction, res) }() @@ -2021,6 +2001,19 @@ func (es *ExecutionState) postTxHookCall(txn *types.Transaction, txResult *Execu } } +func (es *ExecutionState) EnableVmTracing() { + es.evm.Config.Tracer = &tracing.Hooks{ + OnOpcode: func( + pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error, + ) { + for i, item := range scope.StackData() { + fmt.Printf(" %d: %s\n", i, item.String()) + } + fmt.Printf("%04x: %s\n", pc, vm.OpCode(op).String()) + }, + } +} + func VerboseTracingHooks(logger logging.Logger) *tracing.Hooks { return &tracing.Hooks{ OnOpcode: func( diff --git a/nil/internal/execution/zerostate.go b/nil/internal/execution/zerostate.go index f257475b6..680860855 100644 --- a/nil/internal/execution/zerostate.go +++ b/nil/internal/execution/zerostate.go @@ -81,6 +81,23 @@ func CreateDefaultZeroStateConfig(mainPublicKey []byte) (*ZeroStateConfig, error return zeroStateConfig, nil } +func AddRelayersToZeroStateConfig(zeroStateConfig *ZeroStateConfig, shardsNum int) { + for i := range shardsNum { + zeroStateConfig.Contracts = append(zeroStateConfig.Contracts, &ContractDescr{ + Name: fmt.Sprintf("Relayer_%d", i), + Contract: "Relayer", + Address: types.ShardAndHexToAddress(types.ShardId(i), types.RelayerPureAddress), + Value: types.Value0, + }) + zeroStateConfig.Contracts = append(zeroStateConfig.Contracts, &ContractDescr{ + Name: fmt.Sprintf("TokenManager_%d", i), + Contract: "TokenManager", + Address: types.ShardAndHexToAddress(types.ShardId(i), types.TokenManagerPureAddress), + Value: types.Value0, + }) + } +} + func (cfg *ZeroStateConfig) GetValidators() []config.ListValidators { return cfg.ConfigParams.Validators.Validators } diff --git a/nil/internal/types/address.go b/nil/internal/types/address.go index 466f66375..2a17621fa 100644 --- a/nil/internal/types/address.go +++ b/nil/internal/types/address.go @@ -32,6 +32,8 @@ var ( UsdcFaucetAddress = ShardAndHexToAddress(BaseShardId, "111111111111111111111111111111111115") L1BlockInfoAddress = ShardAndHexToAddress(MainShardId, "222222222222222222222222222222222222") GovernanceAddress = ShardAndHexToAddress(MainShardId, "777777777777777777777777777777777777") + RelayerPureAddress = "333333333333333333333333333333333333" + TokenManagerPureAddress = "444444444444444444444444444444444444" ) func GetTokenName(addr TokenId) string { diff --git a/nil/internal/vm/precompiled.go b/nil/internal/vm/precompiled.go index f2f097084..e891df00f 100644 --- a/nil/internal/vm/precompiled.go +++ b/nil/internal/vm/precompiled.go @@ -654,6 +654,7 @@ func (c *manageToken) RequiredGas([]byte, StateDBReadOnly) (uint64, error) { } func (c *manageToken) Run(state StateDB, input []byte, value *uint256.Int, caller ContractRef) ([]byte, error) { + panic("Deprecated") if len(input) < 4 { return nil, types.NewVmError(types.ErrorPrecompileTooShortCallData) } diff --git a/nil/services/rpc/rawapi/internal/local_account.go b/nil/services/rpc/rawapi/internal/local_account.go index c5dcf2022..6233c3624 100644 --- a/nil/services/rpc/rawapi/internal/local_account.go +++ b/nil/services/rpc/rawapi/internal/local_account.go @@ -4,13 +4,15 @@ import ( "context" "errors" "fmt" - "github.com/NilFoundation/nil/nil/common" + "github.com/NilFoundation/nil/nil/internal/config" + "github.com/NilFoundation/nil/nil/internal/contracts" "github.com/NilFoundation/nil/nil/internal/db" "github.com/NilFoundation/nil/nil/internal/execution" "github.com/NilFoundation/nil/nil/internal/mpt" "github.com/NilFoundation/nil/nil/internal/types" rawapitypes "github.com/NilFoundation/nil/nil/services/rpc/rawapi/types" + "math/big" ) var errBlockNotFound = errors.New("block not found") @@ -75,7 +77,96 @@ func (api *localShardApiRo) GetCode( return code, nil } -func (api *localShardApiRo) GetTokens( +type token struct { + Token types.Address + Balance *big.Int +} + +func (api *localShardApi) GetTokens( + ctx context.Context, + address types.Address, + blockReference rawapitypes.BlockReference, +) (map[types.TokenId]types.Value, error) { + abi, err := contracts.GetAbi(contracts.NameTokenManager) + if err != nil { + return nil, fmt.Errorf("cannot get ABI: %w", err) + } + + calldata, err := abi.Pack("getTokens", address) + if err != nil { + return nil, fmt.Errorf("cannot pack calldata: %w", err) + } + + tokenManagerAddr := types.ShardAndHexToAddress(address.ShardId(), types.TokenManagerPureAddress) + + ret, err := api.CallGetter(ctx, tokenManagerAddr, calldata) + if err != nil { + return nil, fmt.Errorf("failed to call getter: %w", err) + } + + var tokens []token + err = abi.UnpackIntoInterface(&tokens, "getTokens", ret) + if err != nil { + return nil, fmt.Errorf("failed to unpack response: %w", err) + } + + res := make(map[types.TokenId]types.Value) + for t := range tokens { + res[types.TokenId(tokens[t].Token)] = types.NewValueFromBigMust(tokens[t].Balance) + } + return res, nil +} + +func (api *localShardApi) CallGetter( + ctx context.Context, + address types.Address, + calldata []byte, +) ([]byte, error){ + tx, err := api.db.CreateRoTx(ctx) + if err != nil { + return nil, err + } + defer tx.Rollback() + + block, _, err := db.ReadLastBlock(tx, address.ShardId()) + if err != nil { + return nil, fmt.Errorf("failed to read last block: %w", err) + } + + cfgAccessor, err := config.NewConfigReader(tx, &block.MainShardHash) + if err != nil { + return nil, fmt.Errorf("failed to create config accessor: %w", err) + } + + es, err := execution.NewExecutionState(tx, address.ShardId(), execution.StateParams{ + Block: block, + ConfigAccessor: cfgAccessor, + Mode: execution.ModeReadOnly, + }) + if err != nil { + return nil, err + } + + extTxn := &types.ExternalTransaction{ + FeeCredit: types.GasToValue(types.DefaultMaxGasInBlock.Uint64()), + MaxFeePerGas: types.MaxFeePerGasDefault, + To: address, + Data: calldata, + } + + txn := extTxn.ToTransaction() + + payer := execution.NewDummyPayer() + + es.AddInTransaction(txn) + res := es.HandleTransaction(ctx, txn, payer) + if res.Failed() { + return nil, fmt.Errorf("transaction failed: %w", res.GetError()) + } + return res.ReturnData, nil +} + +func (api *localShardApi) GetTokens1( ctx context.Context, address types.Address, blockReference rawapitypes.BlockReference, diff --git a/nil/tests/multitoken/multitoken_test.go b/nil/tests/multitoken/multitoken_test.go index a1186e8ba..0c110837c 100644 --- a/nil/tests/multitoken/multitoken_test.go +++ b/nil/tests/multitoken/multitoken_test.go @@ -107,6 +107,8 @@ func (s *SuiteMultiTokenRpc) SetupTest() { HttpUrl: rpc.GetSockPath(s.T()), ZeroState: zerostateCfg, RunMode: nilservice.CollatorsOnlyRunMode, + + DisableConsensus: true, }) } @@ -206,6 +208,7 @@ func (s *SuiteMultiTokenRpc) TestMultiToken() { //nolint s.Equal(types.NewValueFromUint64(100), tokens[*token1.id]) }) }) + return s.Run("Send from Wallet1 to Wallet2 via asyncCall", func() { receipt := s.SendTransactionViaSmartAccountNoCheck( @@ -238,6 +241,7 @@ func (s *SuiteMultiTokenRpc) TestMultiToken() { //nolint }) }) + s.Run("TestDeployWithToken", func() { tokens := []types.TokenBalance{{Token: *token1.id, Balance: types.NewValueFromUint64(10)}} contractCode, _ := s.LoadContract(common.GetAbsolutePath("../contracts/increment.sol"), "Incrementer") diff --git a/nil/tests/rpc_suite.go b/nil/tests/rpc_suite.go index 2b79281e7..195a84ce0 100644 --- a/nil/tests/rpc_suite.go +++ b/nil/tests/rpc_suite.go @@ -87,6 +87,7 @@ func (s *RpcSuite) Start(cfg *nilservice.Config) { cfg.ZeroState, err = execution.CreateDefaultZeroStateConfig(execution.MainPublicKey) s.Require().NoError(err) } + execution.AddRelayersToZeroStateConfig(cfg.ZeroState, int(s.ShardsNum)) var serviceInterop chan nilservice.ServiceInterop if cfg.RunMode == nilservice.CollatorsOnlyRunMode { diff --git a/nil/tests/timeouts_no_race.go b/nil/tests/timeouts_no_race.go index d82fb2086..7b9779681 100644 --- a/nil/tests/timeouts_no_race.go +++ b/nil/tests/timeouts_no_race.go @@ -5,9 +5,9 @@ package tests import "time" const ( - ReceiptWaitTimeout = 15 * time.Second + ReceiptWaitTimeout = 15 * time.Minute ReceiptPollInterval = 250 * time.Millisecond - BlockWaitTimeout = 10 * time.Second + BlockWaitTimeout = 10 * time.Minute BlockPollInterval = 100 * time.Millisecond ShardTickWaitTimeout = 30 * time.Second ShardTickPollInterval = 1 * time.Second diff --git a/smart-contracts/contracts/IterableMapping.sol b/smart-contracts/contracts/IterableMapping.sol new file mode 100644 index 000000000..7ab878689 --- /dev/null +++ b/smart-contracts/contracts/IterableMapping.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +library IterableMapping { + // Iterable mapping from address to uint; + struct Map { + address[] keys; + mapping(address => uint256) values; + mapping(address => uint256) indexOf; + mapping(address => bool) inserted; + } + + function get(Map storage map, address key) internal view returns (uint256) { + return map.values[key]; + } + + function getKeyAtIndex(Map storage map, uint256 index) internal view returns (address) { + return map.keys[index]; + } + + function size(Map storage map) internal view returns (uint256) { + return map.keys.length; + } + + function set(Map storage map, address key, uint256 val) internal { + if (map.inserted[key]) { + map.values[key] = val; + } else { + map.inserted[key] = true; + map.values[key] = val; + map.indexOf[key] = map.keys.length; + map.keys.push(key); + } + } + + function remove(Map storage map, address key) internal { + if (!map.inserted[key]) { + return; + } + + delete map.inserted[key]; + delete map.values[key]; + + uint256 index = map.indexOf[key]; + address lastKey = map.keys[map.keys.length - 1]; + + map.indexOf[lastKey] = index; + delete map.indexOf[key]; + + map.keys[index] = lastKey; + map.keys.pop(); + } +} \ No newline at end of file diff --git a/smart-contracts/contracts/Nil.sol b/smart-contracts/contracts/Nil.sol index 0c10fb134..cb64b794f 100644 --- a/smart-contracts/contracts/Nil.sol +++ b/smart-contracts/contracts/Nil.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; +import "./Relayer.sol"; + // TokenId is a type that represents a unique token identifier. type TokenId is address; @@ -158,8 +160,25 @@ library Nil { Token[] memory tokens, bytes memory callData ) internal { - __Precompile__(ASYNC_CALL).precompileAsyncCall{value: value}(false, forwardKind, dst, refundTo, - bounceTo, feeCredit, tokens, callData, 0, 0); + Relayer(getRelayerAddress()).sendTx(dst, refundTo, feeCredit, forwardKind, value, tokens, callData); + } + + function getRelayerAddress() internal view returns (address) { + uint160 addr = uint160(getShardId(address(this))) << (18 * 8); + addr |= uint160(0x333333333333333333333333333333333333); + return address(addr); + } + + function getRelayerAddress(uint shardId) internal view returns (address) { + uint160 addr = uint160(shardId) << (18 * 8); + addr |= uint160(0x333333333333333333333333333333333333); + return address(addr); + } + + function getTokenManagerAddress() internal view returns (address) { + uint160 addr = uint160(getShardId(address(this))) << (18 * 8); + addr |= uint160(0x444444444444444444444444444444444444); + return address(addr); } /** @@ -428,7 +447,7 @@ abstract contract NilBounceable is NilBase { // WARNING: User should never use this contract directly. contract __Precompile__ { // if mint flag is set to false, token will be burned instead - function precompileManageToken(uint256 amount, bool mint) public returns(bool) {} + function precompileManageToken(uint256 amount, bool mint, TokenId token) public returns(bool) {} function precompileGetTokenBalance(TokenId id, address addr) public view returns(uint256) {} function precompileAsyncCall(bool, uint8, address, address, address, uint, Nil.Token[] memory, bytes memory, uint256, uint) public payable returns(bool) {} function precompileSendTokens(address, Nil.Token[] memory) public returns(bool) {} diff --git a/smart-contracts/contracts/NilTokenBase.sol b/smart-contracts/contracts/NilTokenBase.sol index a8d8f576f..7054d5cda 100644 --- a/smart-contracts/contracts/NilTokenBase.sol +++ b/smart-contracts/contracts/NilTokenBase.sol @@ -20,7 +20,7 @@ abstract contract NilTokenBase is NilBase { * @return The total supply of the token. */ function getTokenTotalSupply() public view returns(uint) { - return totalSupply; + return TokenManager(Nil.getTokenManagerAddress()).totalSupply(address(this)); } /** @@ -88,8 +88,7 @@ abstract contract NilTokenBase is NilBase { * @param amount The amount of token to mint. */ function mintTokenInternal(uint256 amount) internal { - bool success = __Precompile__(Nil.MANAGE_TOKEN).precompileManageToken(amount, true); - require(success, "Mint failed"); + TokenManager(Nil.getTokenManagerAddress()).mint(address(this), amount); totalSupply += amount; } @@ -99,9 +98,7 @@ abstract contract NilTokenBase is NilBase { * @param amount The amount of token to mint. */ function burnTokenInternal(uint256 amount) internal { - require(totalSupply >= amount, "Burn failed: not enough tokens"); - bool success = __Precompile__(Nil.MANAGE_TOKEN).precompileManageToken(amount, false); - require(success, "Burn failed"); + TokenManager(Nil.getTokenManagerAddress()).burn(address(this), amount); totalSupply -= amount; } diff --git a/smart-contracts/contracts/Relayer.sol b/smart-contracts/contracts/Relayer.sol new file mode 100644 index 000000000..5bbbd251e --- /dev/null +++ b/smart-contracts/contracts/Relayer.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +import "./Nil.sol"; +import "./TokenManager.sol"; + + +contract Relayer { + + function sendTx( + address dst, + address refundTo, + uint feeCredit, + uint8 forwardKind, + uint value, + Nil.Token[] memory tokens, + bytes memory callData + ) public { + for (uint i = 0; i < tokens.length; i++) { + TokenManager(Nil.getTokenManagerAddress()).deduct(msg.sender, TokenId.unwrap(tokens[i].id), tokens[i].amount); + } + bytes memory data = abi.encodeWithSelector(this.__receiveTx.selector, dst, refundTo, feeCredit, 0, value, tokens, callData); + __Precompile__(Nil.ASYNC_CALL).precompileAsyncCall{value: value}( + false, + forwardKind, + Nil.getRelayerAddress(Nil.getShardId(dst)), + refundTo, + refundTo, + feeCredit, + tokens, + data); + } + + function __receiveTx( + address dst, + address /*refundTo*/, + uint /*feeCredit*/, + uint8 /*forwardKind*/, + uint value, + Nil.Token[] memory tokens, + bytes memory callData + ) public { + for (uint i = 0; i < tokens.length; i++) { + TokenManager(Nil.getTokenManagerAddress()).credit(dst, TokenId.unwrap(tokens[i].id), tokens[i].amount); + } + (bool success, bytes memory data) = dst.call{value: value}(callData); + require(success, "Call failed"); + } +} diff --git a/smart-contracts/contracts/TokenManager.sol b/smart-contracts/contracts/TokenManager.sol new file mode 100644 index 000000000..6738fa2cb --- /dev/null +++ b/smart-contracts/contracts/TokenManager.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +import "./Nil.sol"; +import "./IterableMapping.sol"; + +contract TokenManager { + + event TokenMinted(address indexed sender, address indexed token, uint256 amount); + event TokenBurned(address indexed sender, address indexed token, uint256 amount); + + mapping(address => IterableMapping.Map) tokensMap; + mapping(address => uint256) totalSupplyMap; + + function deduct(address from, address token, uint256 amount) external { + require(Nil.getRelayerAddress() == msg.sender, "Only relayer can deduct"); + uint256 oldValue = IterableMapping.get(tokensMap[from], token); + require(oldValue >= amount, "Insufficient token balance"); + IterableMapping.set(tokensMap[from], token, oldValue - amount); + } + + function credit(address to, address token, uint256 amount) external { + require(Nil.getRelayerAddress() == msg.sender, "Only relayer can credit"); + uint256 oldValue = IterableMapping.get(tokensMap[to], token); + IterableMapping.set(tokensMap[to], token, oldValue + amount); + } + + function burn(address token, uint256 amount) external { + require(msg.sender == token, "Only owner can burn"); + uint256 oldValue = IterableMapping.get(tokensMap[msg.sender], token); + require(oldValue >= amount, "Insufficient token balance"); + + IterableMapping.set(tokensMap[msg.sender], token, oldValue - amount); + totalSupplyMap[token] -= amount; + + emit TokenBurned(msg.sender, token, amount); + } + + function mint(address token, uint256 amount) external { + require(msg.sender == token, "Only owner can mint"); + uint256 oldValue = IterableMapping.get(tokensMap[msg.sender], token); + + IterableMapping.set(tokensMap[msg.sender], token, oldValue + amount); + totalSupplyMap[token] += amount; + + emit TokenMinted(msg.sender, token, amount); + } + + function totalSupply(address token) view external returns (uint256) { + return totalSupplyMap[token]; + } + + function getTokens(address account) external view returns (Nil.Token[] memory) { + uint256 length = IterableMapping.size(tokensMap[account]); + Nil.Token[] memory tokens = new Nil.Token[](length); + for (uint256 i = 0; i < length; i++) { + address token = IterableMapping.getKeyAtIndex(tokensMap[account], i); + uint256 amount = IterableMapping.get(tokensMap[account], token); + tokens[i] = Nil.Token({id: TokenId.wrap(token), amount: amount}); + } + return tokens; + } +} \ No newline at end of file From 56463affadd007d8e6cb5328fe1f8aa0e6f9b5f1 Mon Sep 17 00:00:00 2001 From: Zerg1996 Date: Thu, 17 Apr 2025 11:33:33 +0200 Subject: [PATCH 2/2] Using tokens in sync transactions --- .../contracts/LendingPool.sol | 4 +- .../deep_dive_into_the_protocol.md | 2 +- .../token-split/contracts/tokenSplitter.sol | 2 +- docs/nil/guides/app-migration.mdx | 1 + docs/nil/smart-contracts/pre-compiles.mdx | 4 +- docs/tests/AsyncToken.sol | 2 +- docs/tests/SwapMatch.sol | 2 +- docs/tests/SwapMatchPure.sol | 2 +- .../solidity/tests/RequestResponseTest.sol | 2 +- nil/contracts/solidity/tests/TokensTest.sol | 19 +--- nil/internal/vm/precompiled.go | 48 +--------- .../rpc/rawapi/internal/local_account.go | 92 ++++++++++++++++++- nil/tests/multitoken/multitoken_test.go | 57 ++++++------ .../contracts/ContractFactoryTest.sol | 2 +- smart-contracts/contracts/Nil.sol | 14 +-- smart-contracts/contracts/TokenManager.sol | 21 +++++ uniswap/contracts/UniswapV2Router01.sol | 12 +-- 17 files changed, 166 insertions(+), 120 deletions(-) diff --git a/academy/lending-protocol/contracts/LendingPool.sol b/academy/lending-protocol/contracts/LendingPool.sol index 54dff4982..de07ef8f8 100644 --- a/academy/lending-protocol/contracts/LendingPool.sol +++ b/academy/lending-protocol/contracts/LendingPool.sol @@ -40,7 +40,7 @@ contract LendingPool is NilBase, NilTokenBase, NilAwaitable { /// @dev The deposited tokens are recorded in the GlobalLedger via an asynchronous call. function deposit() public payable { /// Retrieve the tokens being sent in the transaction - Nil.Token[] memory tokens = Nil.txnTokens(); + Nil.Token[] memory tokens = Nil.txnTokens(); // TODO: [PoC] Tokens remove it /// @notice Encoding the call to the GlobalLedger to record the deposit /// @dev The deposit details (user address, token type, and amount) are encoded for GlobalLedger. @@ -215,7 +215,7 @@ contract LendingPool is NilBase, NilTokenBase, NilAwaitable { function repayLoan() public payable { /// @notice Retrieve the tokens being sent in the transaction /// @dev Retrieves the tokens involved in the repayment. - Nil.Token[] memory tokens = Nil.txnTokens(); + Nil.Token[] memory tokens = Nil.txnTokens(); // TODO: [PoC] Tokens remove it /// @notice Prepare to query the loan details from GlobalLedger /// @dev Fetches the loan details of the borrower to proceed with repayment. diff --git a/academy/lending-protocol/deep_dive_into_the_protocol.md b/academy/lending-protocol/deep_dive_into_the_protocol.md index 0e3db3723..8eb390c8d 100644 --- a/academy/lending-protocol/deep_dive_into_the_protocol.md +++ b/academy/lending-protocol/deep_dive_into_the_protocol.md @@ -30,7 +30,7 @@ Example: ```solidity function deposit() public payable { - Nil.Token[] memory tokens = Nil.txnTokens(); + Nil.Token[] memory tokens = Nil.txnTokens(); // TODO: [PoC] Tokens remove it bytes memory callData = abi.encodeWithSignature( "recordDeposit(address,address,uint256)", msg.sender, diff --git a/academy/token-split/contracts/tokenSplitter.sol b/academy/token-split/contracts/tokenSplitter.sol index 6463097f8..a9e304654 100644 --- a/academy/token-split/contracts/tokenSplitter.sol +++ b/academy/token-split/contracts/tokenSplitter.sol @@ -45,7 +45,7 @@ contract TokenSplitter is NilBase, NilTokenBase, Ownable, ReentrancyGuard { if (_recipients.length == 0) revert NoRecipientsSpecified(); if (_recipients.length != _amounts.length) revert ArrayLengthMismatch(); - Nil.Token[] memory tokens = Nil.txnTokens(); + Nil.Token[] memory tokens = Nil.txnTokens(); // TODO: [PoC] Tokens remove it uint256 totalAmountToSend = 0; for (uint256 i = 0; i < _amounts.length; i++) { diff --git a/docs/nil/guides/app-migration.mdx b/docs/nil/guides/app-migration.mdx index 71a380b74..8e89fb1eb 100644 --- a/docs/nil/guides/app-migration.mdx +++ b/docs/nil/guides/app-migration.mdx @@ -319,5 +319,6 @@ Note that it is also possible to use async calls to send custom tokens between c ```solidity file=../../tests/AsyncToken.sol start=startAsyncTokenContract end=endAsyncTokenContract ``` + // TODO: [PoC] Tokens remove it Tokens can be extracted from any transaction by using `Nil.txnTokens()` and then passed to the `asyncCallWithTokens()` method. When migrating a dApp to =nil;, do not hesitate to deploy contracts on different shards: they will still be able to send and receive tokens from contracts on other shards. diff --git a/docs/nil/smart-contracts/pre-compiles.mdx b/docs/nil/smart-contracts/pre-compiles.mdx index 3802cbc13..ac5a4e508 100644 --- a/docs/nil/smart-contracts/pre-compiles.mdx +++ b/docs/nil/smart-contracts/pre-compiles.mdx @@ -122,10 +122,10 @@ The function shows how many tokens with the given `id` are held by the contract ## `GET_TRANSACTION_TOKENS` -`GET_TRANSACTION_TOKENS` is the pre-compile used in the `txnTokens()` function. +`GET_TRANSACTION_TOKENS` is the pre-compile used in the `txnTokens()` function. // TODO: [PoC] Tokens remove it ```solidity showLineNumbers -function txnTokens() internal returns(Token[] memory) +function txnTokens() internal returns(Token[] memory) // TODO: [PoC] Tokens remove it ``` The function returns the list of tokens for the current transaction. diff --git a/docs/tests/AsyncToken.sol b/docs/tests/AsyncToken.sol index 6ed4f383b..9198cd5a9 100644 --- a/docs/tests/AsyncToken.sol +++ b/docs/tests/AsyncToken.sol @@ -7,7 +7,7 @@ import "@nilfoundation/smart-contracts/contracts/Nil.sol"; contract AsyncTokenSender { function sendTokenAsync(uint amount, address dst) public { - Nil.Token[] memory tokens = Nil.txnTokens(); + Nil.Token[] memory tokens = Nil.txnTokens(); // TODO: [PoC] Tokens remove it Nil.asyncCallWithTokens( dst, msg.sender, diff --git a/docs/tests/SwapMatch.sol b/docs/tests/SwapMatch.sol index 464c1c8d1..5a03889bf 100644 --- a/docs/tests/SwapMatch.sol +++ b/docs/tests/SwapMatch.sol @@ -82,7 +82,7 @@ contract SwapMatch is NilBase { //Create a new swap request SwapRequest memory newSwapRequest = SwapRequest({ initiator: msg.sender, - token: Nil.txnTokens()[0], + token: Nil.txnTokens()[0], // TODO: [PoC] Tokens remove it secondTokenId: _secondTokenId, desiredSecondTokenAmount: _desiredSecondTokenAmount, isMatched: false diff --git a/docs/tests/SwapMatchPure.sol b/docs/tests/SwapMatchPure.sol index e0334ac83..62234cb53 100644 --- a/docs/tests/SwapMatchPure.sol +++ b/docs/tests/SwapMatchPure.sol @@ -75,7 +75,7 @@ contract SwapMatch is NilBase { //Create a new swap request SwapRequest memory newSwapRequest = SwapRequest({ initiator: msg.sender, - token: Nil.txnTokens()[0], + token: Nil.txnTokens()[0], // TODO: [PoC] Tokens remove it secondTokenId: _secondTokenId, desiredSecondTokenAmount: _desiredSecondTokenAmount, isMatched: false diff --git a/nil/contracts/solidity/tests/RequestResponseTest.sol b/nil/contracts/solidity/tests/RequestResponseTest.sol index 6b44ec316..4eb7f074a 100644 --- a/nil/contracts/solidity/tests/RequestResponseTest.sol +++ b/nil/contracts/solidity/tests/RequestResponseTest.sol @@ -254,7 +254,7 @@ contract RequestResponseTest is NilTokenBase, NilAwaitable { require(success, "Request should be successful"); uint ctxValue = abi.decode(context, (uint)); require(ctxValue == uint(11111), "Context value should be the same"); - require(Nil.txnTokens().length == 0, "Tokens should be empty"); + require(Nil.txnTokens().length == 0, "Tokens should be empty"); // TODO: [PoC] Tokens remove it } /** diff --git a/nil/contracts/solidity/tests/TokensTest.sol b/nil/contracts/solidity/tests/TokensTest.sol index 00fecb0ca..e0476ab9a 100644 --- a/nil/contracts/solidity/tests/TokensTest.sol +++ b/nil/contracts/solidity/tests/TokensTest.sol @@ -77,22 +77,7 @@ contract TokensTest is NilTokenBase { } function testTransactionTokens(Nil.Token[] memory tokens) public payable { - Nil.Token[] memory transactionTokens = Nil.txnTokens(); - require( - transactionTokens.length == tokens.length, - "Tokens length mismatch" - ); - for (uint i = 0; i < tokens.length; i++) { - require( - TokenId.unwrap(transactionTokens[i].id) == - TokenId.unwrap(tokens[i].id), - "Tokens id mismatch" - ); - require( - transactionTokens[i].amount == tokens[i].amount, - "Tokens amount mismatch" - ); - } + // TODO: [PoC] Tokens fix it } function receiveTokens(bool fail) public payable { @@ -126,7 +111,7 @@ contract TokensTest is NilTokenBase { event tokenTxnBalance(uint256 balance); function checkIncomingToken(TokenId id) public payable { - emit tokenTxnBalance(Nil.txnTokens()[0].amount); + emit tokenTxnBalance(Nil.txnTokens()[0].amount); // TODO: [PoC] Tokens remove it emit tokenBalance(Nil.tokenBalance(address(this), id)); } diff --git a/nil/internal/vm/precompiled.go b/nil/internal/vm/precompiled.go index e891df00f..44ec27db0 100644 --- a/nil/internal/vm/precompiled.go +++ b/nil/internal/vm/precompiled.go @@ -128,7 +128,6 @@ var PrecompiledContractsPrague = map[types.Address]PrecompiledContract{ CheckIsInternalAddress: &checkIsInternal{}, ManageTokenAddress: &manageToken{}, TokenBalanceAddress: &tokenBalance{}, - SendTokensAddress: &sendTokenSync{}, TransactionTokensAddress: &getTransactionTokens{}, GetGasPriceAddress: &getGasPrice{}, ConfigParamAddress: &configParam{}, @@ -189,7 +188,7 @@ func (a *simple) Run( _ StateDBReadOnly, /* state */ input []byte, _ *uint256.Int, /* value */ - _ ContractRef, /* caller */ + _ ContractRef, /* caller */ ) ([]byte, error) { return a.contract.Run(input) } @@ -751,51 +750,6 @@ func (a *tokenBalance) Run( return res, nil } -type sendTokenSync struct{} - -var _ ReadWritePrecompiledContract = (*sendTokenSync)(nil) - -func (c *sendTokenSync) RequiredGas([]byte, StateDBReadOnly) (uint64, error) { - return 10, nil -} - -func (c *sendTokenSync) Run(state StateDB, input []byte, value *uint256.Int, caller ContractRef) ([]byte, error) { - if len(input) < 4 { - return nil, types.NewVmError(types.ErrorPrecompileTooShortCallData) - } - - // Unpack arguments, skipping the first 4 bytes (function selector) - args, err := getPrecompiledMethod("precompileSendTokens").Inputs.Unpack(input[4:]) - if err != nil { - return nil, types.NewVmVerboseError(types.ErrorAbiUnpackFailed, err.Error()) - } - if len(args) != 2 { - return nil, types.NewVmError(types.ErrorPrecompileWrongNumberOfArguments) - } - - // Get destination address - addr, ok := args[0].(types.Address) - check.PanicIfNotf(ok, "sendTokenSync failed: addr argument is not an address") - - if caller.Address().ShardId() != addr.ShardId() { - return nil, fmt.Errorf("sendTokenSync: %w: %s -> %s", - ErrCrossShardTransaction, caller.Address().ShardId(), addr.ShardId()) - } - - // Get tokens - tokens, err := extractTokens(args[1]) - if err != nil { - return nil, types.NewVmVerboseError(types.ErrorPrecompileInvalidTokenArray, "sendTokenSync") - } - - state.SetTokenTransfer(tokens) - - res := make([]byte, 32) - res[31] = 1 - - return res, nil -} - type getTransactionTokens struct{} var _ ReadOnlyPrecompiledContract = (*getTransactionTokens)(nil) diff --git a/nil/services/rpc/rawapi/internal/local_account.go b/nil/services/rpc/rawapi/internal/local_account.go index 6233c3624..f55aa90ca 100644 --- a/nil/services/rpc/rawapi/internal/local_account.go +++ b/nil/services/rpc/rawapi/internal/local_account.go @@ -121,7 +121,7 @@ func (api *localShardApi) CallGetter( ctx context.Context, address types.Address, calldata []byte, -) ([]byte, error){ +) ([]byte, error) { tx, err := api.db.CreateRoTx(ctx) if err != nil { return nil, err @@ -148,10 +148,10 @@ func (api *localShardApi) CallGetter( } extTxn := &types.ExternalTransaction{ - FeeCredit: types.GasToValue(types.DefaultMaxGasInBlock.Uint64()), + FeeCredit: types.GasToValue(types.DefaultMaxGasInBlock.Uint64()), MaxFeePerGas: types.MaxFeePerGasDefault, - To: address, - Data: calldata, + To: address, + Data: calldata, } txn := extTxn.ToTransaction() @@ -170,6 +170,90 @@ func (api *localShardApi) GetTokens1( ctx context.Context, address types.Address, blockReference rawapitypes.BlockReference, +) (map[types.TokenId]types.Value, error) { + abi, err := contracts.GetAbi(contracts.NameTokenManager) + if err != nil { + return nil, fmt.Errorf("cannot get ABI: %w", err) + } + + calldata, err := abi.Pack("getTokens", address) + if err != nil { + return nil, fmt.Errorf("cannot pack calldata: %w", err) + } + + tokenManagerAddr := types.ShardAndHexToAddress(address.ShardId(), types.TokenManagerPureAddress) + + ret, err := api.CallGetter(ctx, tokenManagerAddr, calldata) + if err != nil { + return nil, fmt.Errorf("failed to call getter: %w", err) + } + + var tokens []token + err = abi.UnpackIntoInterface(&tokens, "getTokens", ret) + if err != nil { + return nil, fmt.Errorf("failed to unpack response: %w", err) + } + + res := make(map[types.TokenId]types.Value) + for t := range tokens { + res[types.TokenId(tokens[t].Token)] = types.NewValueFromBigMust(tokens[t].Balance) + } + return res, nil +} + +func (api *LocalShardApi) CallGetter( + ctx context.Context, + address types.Address, + calldata []byte, +) ([]byte, error) { + tx, err := api.db.CreateRoTx(ctx) + if err != nil { + return nil, err + } + defer tx.Rollback() + + block, _, err := db.ReadLastBlock(tx, address.ShardId()) + if err != nil { + return nil, fmt.Errorf("failed to read last block: %w", err) + } + + cfgAccessor, err := config.NewConfigReader(tx, &block.MainShardHash) + if err != nil { + return nil, fmt.Errorf("failed to create config accessor: %w", err) + } + + es, err := execution.NewExecutionState(tx, address.ShardId(), execution.StateParams{ + Block: block, + ConfigAccessor: cfgAccessor, + Mode: execution.ModeReadOnly, + }) + if err != nil { + return nil, err + } + + extTxn := &types.ExternalTransaction{ + FeeCredit: types.GasToValue(types.DefaultMaxGasInBlock.Uint64()), + MaxFeePerGas: types.MaxFeePerGasDefault, + To: address, + Data: calldata, + } + + txn := extTxn.ToTransaction() + + payer := execution.NewDummyPayer() + + es.AddInTransaction(txn) + res := es.HandleTransaction(ctx, txn, payer) + if res.Failed() { + return nil, fmt.Errorf("transaction failed: %w", res.GetError()) + } + return res.ReturnData, nil +} + +func (api *LocalShardApi) GetTokens1( + ctx context.Context, + address types.Address, + blockReference rawapitypes.BlockReference, ) (map[types.TokenId]types.Value, error) { shardId := address.ShardId() if shardId != api.shardId() { diff --git a/nil/tests/multitoken/multitoken_test.go b/nil/tests/multitoken/multitoken_test.go index 0c110837c..8461436f8 100644 --- a/nil/tests/multitoken/multitoken_test.go +++ b/nil/tests/multitoken/multitoken_test.go @@ -241,7 +241,6 @@ func (s *SuiteMultiTokenRpc) TestMultiToken() { //nolint }) }) - s.Run("TestDeployWithToken", func() { tokens := []types.TokenBalance{{Token: *token1.id, Balance: types.NewValueFromUint64(10)}} contractCode, _ := s.LoadContract(common.GetAbsolutePath("../contracts/increment.sol"), "Incrementer") @@ -346,6 +345,9 @@ func (s *SuiteMultiTokenRpc) TestMultiToken() { //nolint tokenTest1 := CreateTokenId(&s.testAddress1_0) tokenTest2 := CreateTokenId(&s.testAddress1_1) + tokenToSend := types.NewValueFromUint64(5000) + tokenInitial := types.NewValueFromUint64(1_000_000) + defaultFee := types.NewFeePackFromGas(100_000) s.Run("Create tokens for test addresses", func() { s.createTokenForTestContract(tokenTest1, types.NewValueFromUint64(1_000_000), "testToken1") @@ -353,12 +355,13 @@ func (s *SuiteMultiTokenRpc) TestMultiToken() { //nolint }) s.Run("Call testCallWithTokensSync of testAddress1_0", func() { + data, err := s.abiTest.Pack("testCallWithTokensSync", s.testAddress1_1, - []types.TokenBalance{{Token: *tokenTest1.id, Balance: types.NewValueFromUint64(5000)}}) + []types.TokenBalance{{Token: *tokenTest1.id, Balance: tokenToSend}}) s.Require().NoError(err) hash, err := s.Client.SendExternalTransaction( - s.Context, data, s.testAddress1_0, nil, types.NewFeePackFromGas(100_000)) + s.Context, data, s.testAddress1_0, nil, defaultFee) s.Require().NoError(err) receipt := s.WaitForReceipt(hash) s.Require().True(receipt.Success) @@ -366,11 +369,13 @@ func (s *SuiteMultiTokenRpc) TestMultiToken() { //nolint s.Run("Check token is debited from testAddress1_0", func() { tokens, err := s.Client.GetTokens(s.Context, s.testAddress1_0, "latest") s.Require().NoError(err) - s.Equal(types.NewValueFromUint64(1_000_000-5000), tokens[*tokenTest1.id]) + s.Equal(tokenInitial.Sub(tokenToSend), tokens[*tokenTest1.id]) // Check balance via `Nil.tokenBalance` Solidity method + newBalance := tokenInitial.ToBig() + newBalance.Sub(newBalance, tokenToSend.ToBig()) data, err := s.abiTest.Pack( - "checkTokenBalance", types.EmptyAddress, tokenTest1.id, big.NewInt(1_000_000-5000)) + "checkTokenBalance", s.testAddress1_0, tokenTest1.id, newBalance) s.Require().NoError(err) receipt := s.SendExternalTransactionNoCheck(data, s.testAddress1_0) s.Require().True(receipt.Success) @@ -379,7 +384,7 @@ func (s *SuiteMultiTokenRpc) TestMultiToken() { //nolint s.Run("Check token is credited to testAddress1_1", func() { tokens, err := s.Client.GetTokens(s.Context, s.testAddress1_1, "latest") s.Require().NoError(err) - s.Equal(types.NewValueFromUint64(5000), tokens[*tokenTest1.id]) + s.Equal(tokenToSend, tokens[*tokenTest1.id]) }) }) @@ -388,7 +393,7 @@ func (s *SuiteMultiTokenRpc) TestMultiToken() { //nolint s.Run("Try to call with non-existent token", func() { data, err := s.abiTest.Pack("testCallWithTokensSync", s.testAddress1_1, []types.TokenBalance{ - {Token: *tokenTest1.id, Balance: types.NewValueFromUint64(5000)}, + {Token: *tokenTest1.id, Balance: tokenToSend}, {Token: invalidId, Balance: types.NewValueFromUint64(1)}, }) s.Require().NoError(err) @@ -402,23 +407,23 @@ func (s *SuiteMultiTokenRpc) TestMultiToken() { //nolint s.Run("Check token of testAddress1_0", func() { tokens, err := s.Client.GetTokens(s.Context, s.testAddress1_0, "latest") s.Require().NoError(err) - s.Equal(types.NewValueFromUint64(1_000_000-5000), tokens[*tokenTest1.id]) + s.Equal(tokenInitial.Sub(tokenToSend), tokens[*tokenTest1.id]) }) s.Run("Check token of testAddress1_1", func() { tokens, err := s.Client.GetTokens(s.Context, s.testAddress1_1, "latest") s.Require().NoError(err) - s.Equal(types.NewValueFromUint64(5000), tokens[*tokenTest1.id]) + s.Equal(tokenToSend, tokens[*tokenTest1.id]) }) }) s.Run("Call testCallWithTokensAsync of testAddress1_0", func() { data, err := s.abiTest.Pack("testCallWithTokensAsync", s.testAddress1_1, - []types.TokenBalance{{Token: *tokenTest1.id, Balance: types.NewValueFromUint64(5000)}}) + []types.TokenBalance{{Token: *tokenTest1.id, Balance: tokenToSend}}) s.Require().NoError(err) hash, err := s.Client.SendExternalTransaction( - s.Context, data, s.testAddress1_0, nil, types.NewFeePackFromGas(100_000)) + s.Context, data, s.testAddress1_0, nil, defaultFee) s.Require().NoError(err) receipt := s.WaitForReceipt(hash) s.Require().True(receipt.Success) @@ -428,26 +433,26 @@ func (s *SuiteMultiTokenRpc) TestMultiToken() { //nolint s.Run("Check token is debited from testAddress1_0", func() { tokens, err := s.Client.GetTokens(s.Context, s.testAddress1_0, "latest") s.Require().NoError(err) - s.Equal(types.NewValueFromUint64(1_000_000-5000-5000), tokens[*tokenTest1.id]) + s.Equal(tokenInitial.Sub(tokenToSend).Sub(tokenToSend), tokens[*tokenTest1.id]) }) s.Run("Check token is credited to testAddress1_1", func() { tokens, err := s.Client.GetTokens(s.Context, s.testAddress1_1, "latest") s.Require().NoError(err) - s.Equal(types.NewValueFromUint64(5000+5000), tokens[*tokenTest1.id]) + s.Equal(tokenToSend.Add(tokenToSend), tokens[*tokenTest1.id]) }) }) s.Run("Try to call with non-existent token", func() { data, err := s.abiTest.Pack("testCallWithTokensAsync", s.testAddress1_1, []types.TokenBalance{ - {Token: *tokenTest1.id, Balance: types.NewValueFromUint64(5000)}, + {Token: *tokenTest1.id, Balance: tokenToSend}, {Token: invalidId, Balance: types.NewValueFromUint64(1)}, }) s.Require().NoError(err) hash, err := s.Client.SendExternalTransaction( - s.Context, data, s.testAddress1_0, nil, types.NewFeePackFromGas(100_000)) + s.Context, data, s.testAddress1_0, nil, defaultFee) s.Require().NoError(err) receipt := s.WaitForReceipt(hash) s.Require().False(receipt.Success) @@ -456,13 +461,13 @@ func (s *SuiteMultiTokenRpc) TestMultiToken() { //nolint s.Run("Check token of testAddress1_0", func() { tokens, err := s.Client.GetTokens(s.Context, s.testAddress1_0, "latest") s.Require().NoError(err) - s.Equal(types.NewValueFromUint64(1_000_000-5000-5000), tokens[*tokenTest1.id]) + s.Equal(tokenInitial.Sub(tokenToSend).Sub(tokenToSend), tokens[*tokenTest1.id]) }) s.Run("Check token of testAddress1_1", func() { tokens, err := s.Client.GetTokens(s.Context, s.testAddress1_1, "latest") s.Require().NoError(err) - s.Equal(types.NewValueFromUint64(5000+5000), tokens[*tokenTest1.id]) + s.Equal(tokenToSend.Add(tokenToSend), tokens[*tokenTest1.id]) }) }) @@ -470,11 +475,11 @@ func (s *SuiteMultiTokenRpc) TestMultiToken() { //nolint amountTest2 := s.getTokenBalance(&s.testAddress1_1, tokenTest1) s.Run("Call testSendTokensSync", func() { - data, err := s.abiTest.Pack("testSendTokensSync", s.testAddress1_1, big.NewInt(5000), false) + data, err := s.abiTest.Pack("testSendTokensSync", s.testAddress1_1, tokenToSend.ToBig(), false) s.Require().NoError(err) hash, err := s.Client.SendExternalTransaction( - s.Context, data, s.testAddress1_0, nil, types.NewFeePackFromGas(100_000)) + s.Context, data, s.testAddress1_0, nil, defaultFee) s.Require().NoError(err) receipt := s.WaitForReceipt(hash) s.Require().True(receipt.Success) @@ -495,11 +500,11 @@ func (s *SuiteMultiTokenRpc) TestMultiToken() { //nolint }) s.Run("Call testSendTokensSync with fail flag", func() { - data, err := s.abiTest.Pack("testSendTokensSync", s.testAddress1_1, big.NewInt(5000), true) + data, err := s.abiTest.Pack("testSendTokensSync", s.testAddress1_1, tokenToSend.ToBig(), true) s.Require().NoError(err) hash, err := s.Client.SendExternalTransaction( - s.Context, data, s.testAddress1_0, nil, types.NewFeePackFromGas(100_000)) + s.Context, data, s.testAddress1_0, nil, defaultFee) s.Require().NoError(err) receipt := s.WaitForReceipt(hash) s.Require().False(receipt.Success) @@ -507,24 +512,24 @@ func (s *SuiteMultiTokenRpc) TestMultiToken() { //nolint s.Run("Check token of testAddress1_0", func() { tokens, err := s.Client.GetTokens(s.Context, s.testAddress1_0, "latest") s.Require().NoError(err) - s.Equal(amountTest1.Sub64(5000), tokens[*tokenTest1.id]) + s.Equal(amountTest1.Sub(tokenToSend), tokens[*tokenTest1.id]) }) s.Run("Check token of testAddress1_1", func() { tokens, err := s.Client.GetTokens(s.Context, s.testAddress1_1, "latest") s.Require().NoError(err) - s.Equal(amountTest2.Add64(5000), tokens[*tokenTest1.id]) + s.Equal(amountTest2.Add(tokenToSend), tokens[*tokenTest1.id]) }) }) /////////////////////////////////////////////////////////////////////////// // Call `testSendTokensSync` for address in different shard - should fail s.Run("Fail call testSendTokensSync for address in different shard", func() { - data, err := s.abiTest.Pack("testSendTokensSync", s.smartAccountAddress3, big.NewInt(5000), false) + data, err := s.abiTest.Pack("testSendTokensSync", s.smartAccountAddress3, tokenToSend.ToBig(), false) s.Require().NoError(err) hash, err := s.Client.SendExternalTransaction( - s.Context, data, s.testAddress1_0, nil, types.NewFeePackFromGas(100_000)) + s.Context, data, s.testAddress1_0, nil, defaultFee) s.Require().NoError(err) receipt := s.WaitForReceipt(hash) s.Require().False(receipt.Success) @@ -532,7 +537,7 @@ func (s *SuiteMultiTokenRpc) TestMultiToken() { //nolint s.Run("Check token of testAddress1_0", func() { tokens, err := s.Client.GetTokens(s.Context, s.testAddress1_0, "latest") s.Require().NoError(err) - s.Require().Equal(amountTest1.Sub64(5000), tokens[*tokenTest1.id]) + s.Require().Equal(amountTest1.Add(tokenToSend), tokens[*tokenTest1.id]) }) }) } diff --git a/niljs/src/contract-factory/contracts/ContractFactoryTest.sol b/niljs/src/contract-factory/contracts/ContractFactoryTest.sol index e593e81e4..42dbdc9fc 100644 --- a/niljs/src/contract-factory/contracts/ContractFactoryTest.sol +++ b/niljs/src/contract-factory/contracts/ContractFactoryTest.sol @@ -32,7 +32,7 @@ contract ContractFactoryTest { } function receiveToken() public payable { - Nil.Token[] memory tokens = Nil.txnTokens(); + Nil.Token[] memory tokens = Nil.txnTokens(); // TODO: [PoC] Tokens remove it last_token_amount = tokens[0].amount; } } diff --git a/smart-contracts/contracts/Nil.sol b/smart-contracts/contracts/Nil.sol index cb64b794f..e3b71efa1 100644 --- a/smart-contracts/contracts/Nil.sol +++ b/smart-contracts/contracts/Nil.sol @@ -198,9 +198,7 @@ library Nil { Token[] memory tokens, bytes memory callData ) internal returns(bool, bytes memory) { - if (tokens.length > 0) { - __Precompile__(SEND_TOKEN_SYNC).precompileSendTokens(dst, tokens); - } + TokenManager(Nil.getTokenManagerAddress()).transfer(dst, tokens); (bool success, bytes memory returnData) = dst.call{gas: gas, value: value}(callData); return (success, returnData); } @@ -243,7 +241,7 @@ library Nil { * @return Balance of the token. */ function tokenBalance(address addr, TokenId id) internal view returns(uint256) { - return __Precompile__(GET_TOKEN_BALANCE).precompileGetTokenBalance(id, addr); + return TokenManager(Nil.getTokenManagerAddress()).getToken(addr, TokenId.unwrap(id)); } /** @@ -251,7 +249,9 @@ library Nil { * @return Array of tokens from the current transaction. */ function txnTokens() internal returns(Token[] memory) { - return __Precompile__(GET_TRANSACTION_TOKENS).precompileGetTransactionTokens(); + Token[] memory tokens; + require(false, "This function is not implemented"); + return tokens; } /** @@ -447,11 +447,7 @@ abstract contract NilBounceable is NilBase { // WARNING: User should never use this contract directly. contract __Precompile__ { // if mint flag is set to false, token will be burned instead - function precompileManageToken(uint256 amount, bool mint, TokenId token) public returns(bool) {} - function precompileGetTokenBalance(TokenId id, address addr) public view returns(uint256) {} function precompileAsyncCall(bool, uint8, address, address, address, uint, Nil.Token[] memory, bytes memory, uint256, uint) public payable returns(bool) {} - function precompileSendTokens(address, Nil.Token[] memory) public returns(bool) {} - function precompileGetTransactionTokens() public returns(Nil.Token[] memory) {} function precompileGetGasPrice(uint id) public returns(uint256) {} function precompileConfigParam(bool isSet, string calldata name, bytes calldata data) public returns(bytes memory) {} function precompileLog(string memory transaction, int[] memory data) public returns(bool) {} diff --git a/smart-contracts/contracts/TokenManager.sol b/smart-contracts/contracts/TokenManager.sol index 6738fa2cb..4ff1c3483 100644 --- a/smart-contracts/contracts/TokenManager.sol +++ b/smart-contracts/contracts/TokenManager.sol @@ -46,6 +46,23 @@ contract TokenManager { emit TokenMinted(msg.sender, token, amount); } + function transfer( + address dst, + Nil.Token[] memory tokens + ) public { + require(Nil.getShardId(address(msg.sender)) == Nil.getShardId(address(dst)), "Shard ID mismatch"); + for (uint i = 0; i < tokens.length; i++) { + address token = TokenId.unwrap(tokens[i].id); + + uint256 oldValue = IterableMapping.get(tokensMap[msg.sender], token); + require(oldValue >= tokens[i].amount, "Insufficient token balance"); + IterableMapping.set(tokensMap[msg.sender], token, oldValue - tokens[i].amount); + + uint256 oldValueDst = IterableMapping.get(tokensMap[dst], token); + IterableMapping.set(tokensMap[dst], token, oldValueDst + tokens[i].amount); + } + } + function totalSupply(address token) view external returns (uint256) { return totalSupplyMap[token]; } @@ -60,4 +77,8 @@ contract TokenManager { } return tokens; } + + function getToken(address account, address token) external view returns (uint256) { + return IterableMapping.get(tokensMap[account], token); + } } \ No newline at end of file diff --git a/uniswap/contracts/UniswapV2Router01.sol b/uniswap/contracts/UniswapV2Router01.sol index 83b60055b..f0721c29a 100644 --- a/uniswap/contracts/UniswapV2Router01.sol +++ b/uniswap/contracts/UniswapV2Router01.sol @@ -18,7 +18,7 @@ contract UniswapV2Router01 is IUniswapV2Router01, NilTokenBase { } function addLiquidity(address pair, address to) public override { - Nil.Token[] memory tokens = Nil.txnTokens(); + Nil.Token[] memory tokens = Nil.txnTokens(); // TODO: [PoC] Tokens remove it if (tokens.length != 2) { revert("Send only 2 tokens to add liquidity"); } @@ -33,7 +33,7 @@ contract UniswapV2Router01 is IUniswapV2Router01, NilTokenBase { uint amountAMin, uint amountBMin ) public override sameShard(pair) returns (uint amountA, uint amountB) { - Nil.Token[] memory tokens = Nil.txnTokens(); + Nil.Token[] memory tokens = Nil.txnTokens(); // TODO: [PoC] Tokens remove it if (tokens.length != 2) { revert("Send only 2 tokens to add liquidity"); } @@ -121,7 +121,7 @@ contract UniswapV2Router01 is IUniswapV2Router01, NilTokenBase { // **** REMOVE LIQUIDITY **** function removeLiquidity(address pair, address to) public override { - Nil.Token[] memory tokens = Nil.txnTokens(); + Nil.Token[] memory tokens = Nil.txnTokens(); // TODO: [PoC] Tokens remove it if (tokens.length != 1) { revert("UniswapV2Router: should contains only pair token"); } @@ -134,7 +134,7 @@ contract UniswapV2Router01 is IUniswapV2Router01, NilTokenBase { uint /*amountAMin*/, uint /*amountBMin*/ ) public override sameShard(pair) returns (uint amountA, uint amountB) { - Nil.Token[] memory tokens = Nil.txnTokens(); + Nil.Token[] memory tokens = Nil.txnTokens(); // TODO: [PoC] Tokens remove it if (tokens.length != 1) { revert("UniswapV2Router: should contains only pair token"); } @@ -156,7 +156,7 @@ contract UniswapV2Router01 is IUniswapV2Router01, NilTokenBase { uint amount0Out, uint amount1Out ) public override { - Nil.Token[] memory tokens = Nil.txnTokens(); + Nil.Token[] memory tokens = Nil.txnTokens(); // TODO: [PoC] Tokens remove it if (tokens.length != 1) { revert("UniswapV2Router: should contains only pair token"); } @@ -177,7 +177,7 @@ contract UniswapV2Router01 is IUniswapV2Router01, NilTokenBase { uint amountOutMin, address to ) external override sameShard(pair) returns (uint amount) { - Nil.Token[] memory tokens = Nil.txnTokens(); + Nil.Token[] memory tokens = Nil.txnTokens(); // TODO: [PoC] Tokens remove it if (tokens.length != 1) { revert("UniswapV2Router: should contains only pair token"); }