Skip to content

Commit

Permalink
Merge main
Browse files Browse the repository at this point in the history
  • Loading branch information
0xAleksaOpacic committed Sep 3, 2024
2 parents d931bef + e69b235 commit 0f9ad23
Show file tree
Hide file tree
Showing 14 changed files with 6,902 additions and 993 deletions.
4 changes: 2 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ NIL_RPC_ENDPOINT: "http://127.0.0.1:8529"

# Specify the private key used for signing transactions
# This should be a hexadecimal string corresponding to your account's private key
PRIVATE_KEY: "41285f03e8692676bf80a98e4052a008026427a7302ca97cb06edcd60689850b"
PRIVATE_KEY: ""

# Specify the wallet address associated with your private key
# Wallets can be created using the =nil; CLI
# This address will be used for transactions on the =nil; network
NIL_WALLET_ADDR="0x0001f1494b9938E6Fd519441562B2B451eb94fD2"
WALLET_ADDR="0x"
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,6 @@ node_modules

# Hardhat Ignition default folder for deployments against a local node
ignition/deployments

.DS_Store
/dist/
28 changes: 20 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,34 @@ This repository demonstrates how to deploy and interact with smart contracts on
```

## ⚙️ Configuration
Create a `.env` file in the root directory and set the following parameters:
```
NIL_RPC_ENDPOINT=http://127.0.0.1:8529
WALLET_ADDR=0x0001111111111111111111111111111111111111
PRIVATE_KEY=3cac3b0d9297577dfac95fecdd751511446c074a039f001c04585d341423a82a
```
1. Create a `.env` file in the root directory based on the given `.env.example` file.
2. Update the `.env` file with the RPC URL. The default value corresponds to a locally running =nil; node.
3. Create private key and wallet using `nil_cli`:
```
nil_cli keygen new
nil_cli wallet new
```
4. Update the `.env` file with the private key and wallet address.
You can run `npm test` to check if the configuration is correct.
## 🎯 Usage
To deploy and interact with the Incrementer contract, use the following commands:
```
# Deploy the contract
npx hardhat ignition deploy ./ignition/modules/Incrementer.ts --network nil_cluster
npx hardhat ignition deploy ./ignition/modules/Incrementer.ts --network nil

# Interact with the contract
npx hardhat increment --network nil_cluster --contract <Contract Address>
npx hardhat increment --network nil --contract <Contract Address>
```
## 🎯 Testing
To run a test for Incrementer contract, use the following commands:
```
# Deploy the contract
npm run test
```
Make sure to configure .env with RPC and PRIVATE_KEY
## 💪 Contributing
Contributions are always welcome! Please feel free to submit pull requests or open issues to discuss potential changes or improvements.
Expand Down
5 changes: 2 additions & 3 deletions contracts/Incrementer.sol
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./Nil.sol";
import "./nil/Nil.sol";

contract Incrementer is NilBase {
uint256 private value;

event ValueChanged(uint256 newValue);
receive() external payable {}

function increment() public onlyInternal payable {
function increment() public onlyInternal {
value += 1;
emit ValueChanged(value);
}
Expand Down
92 changes: 92 additions & 0 deletions contracts/nil/Minter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.9;

import "./Nil.sol";

contract Minter is NilBase {
struct TokenInfo {
uint256 id;
string name;
address owner;
uint256 totalSupply;
}
bytes pubkey;
mapping(uint256 => TokenInfo) public tokens;
mapping(string => uint256) public namesMap;

receive() external payable {}

constructor(bytes memory _pubkey) {
pubkey = _pubkey;
}

function create(uint256 amount, address owner, string memory name, address sendTo) onlyInternal payable public returns(bool) {
if (owner == address(0)) {
owner = msg.sender;
}
uint256 id = uint256(uint160(owner));
require(id != 0, "Invalid token id");
require(tokens[id].owner == address(0), "Token already exists");

tokens[id] = TokenInfo(id, name, owner, 0);
require(Nil.mintToken(id, amount), "Mint failed");
tokens[id].totalSupply += amount;

if (bytes(name).length != 0) {
namesMap[name] = id;
}

if (sendTo != address(0)) {
withdrawImpl(id, amount, sendTo);
}

return true;
}

function mint(uint256 id, uint256 amount, address sendTo) onlyInternal payable public {
require(tokens[id].owner != address(0), "Token doesn't exist");
require(msg.sender == tokens[id].owner, "Not from owner");
Nil.mintToken(id, amount);
tokens[id].totalSupply += amount;

if (sendTo != address(0)) {
withdrawImpl(id, amount, sendTo);
}
}

function withdraw(uint256 id, uint256 amount, address to) onlyInternal payable public {
require(tokens[id].owner != address(0), "Token doesn't exist");
require(msg.sender == tokens[id].owner, "Not from owner");

uint256 balance = Nil.tokensBalance(address(this), id);
require(balance >= amount, "Insufficient balance");

withdrawImpl(id, amount, to);
}

function withdrawImpl(uint256 id, uint256 amount, address to) onlyInternal internal {
Nil.Token[] memory tokens_ = new Nil.Token[](1);
tokens_[0] = Nil.Token(id, amount);

if (Nil.getShardId(to) == Nil.getShardId(address(this))) {
Nil.syncCall(to, gasleft(), 0, tokens_, "");
} else {
Nil.asyncCall(to, address(0), address(0), 0, Nil.FORWARD_REMAINING, false, 0, tokens_, "");
}
}

// getName returns token name by its id.
function getName(uint256 id) public view returns(string memory) {
require(tokens[id].owner != address(0), "Token does not exist");
return tokens[id].name;
}

// getIdByName returns token id by its name. If token with such name does not exist, returns 0.
function getIdByName(string memory name) public view returns(uint256) {
return namesMap[name];
}

function verifyExternal(uint256 hash, bytes memory signature) external view returns (bool) {
return Nil.validateSignature(pubkey, hash, signature);
}
}
108 changes: 95 additions & 13 deletions contracts/Nil.sol → contracts/nil/Nil.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,43 +10,87 @@ library Nil {
address private constant GET_CURRENCY_BALANCE = address(0xd1);
address private constant SEND_CURRENCY_SYNC = address(0xd2);
address private constant GET_MESSAGE_TOKENS = address(0xd3);
address private constant GET_GAS_PRICE = address(0xd4);
address private constant GET_POSEIDON_HASH = address(0xd5);

address payable public constant MINTER_ADDRESS = payable(address(0x0001222222222222222222222222222222222222));

// The following constants specify from where and how the gas should be taken during async call.
// Forwarding values are calculated in the following order: FORWARD_VALUE, FORWARD_PERCENTAGE, FORWARD_REMAINING.
//
// Take whole remaining gas from inbound message feeCredit. If there are more than one messages with such forward
// kind, the gas will be divided and forwarded in equal parts.
uint8 public constant FORWARD_REMAINING = 0;
// Get a percentage of the available feeCredit.
uint8 public constant FORWARD_PERCENTAGE = 1;
// Get exact value from the available feeCredit.
uint8 public constant FORWARD_VALUE = 2;
// Do not forward gas from inbound message, take gas from the account instead.
uint8 public constant FORWARD_NONE = 3;

// Token is a struct that represents a token with an id and amount.
struct Token {
uint256 id;
uint256 amount;
}

// asyncCall is a function that makes an asynchronous call to `dst` contract.
// Concise version of asyncCall. It implicitly uses FORWARD_REMAINING kind and sets refundTo to inbound message's
// refundTo.
function asyncCall(
address dst,
address bounceTo,
uint value,
bytes memory callData
) internal returns(bool) {
Token[] memory tokens;
return asyncCall(dst, address(0), bounceTo, 0, FORWARD_REMAINING, false, value, tokens, callData);
}

// asyncCall makes an asynchronous call to `dst` contract.
function asyncCall(
address dst,
address refundTo,
address bounceTo,
uint gas,
uint feeCredit,
uint8 forwardKind,
bool deploy,
uint value,
bytes memory callData
) internal returns(bool) {
Token[] memory tokens;
return asyncCall(dst, refundTo, bounceTo, gas, deploy, value, tokens, callData);
return asyncCall(dst, refundTo, bounceTo, feeCredit, forwardKind, deploy, value, tokens, callData);
}

// asyncCall is a function that makes an asynchronous call to `dst` contract.
// This function is used to call a contract with a list of tokens.
// asyncCall makes an asynchronous call to `dst` contract.
function asyncCall(
address dst,
address refundTo,
address bounceTo,
uint gas,
uint feeCredit,
uint8 forwardKind,
bool deploy,
uint value,
Token[] memory tokens,
bytes memory callData
) internal returns(bool) {
bool success = Precompile(ASYNC_CALL).precompileAsyncCall{value: value}(deploy, forwardKind, dst, refundTo,
bounceTo, feeCredit, tokens, callData);
return success;
}

// asyncCall makes an asynchronous call to `dst` contract.
function asyncCall(
address dst,
address refundTo,
address bounceTo,
uint feeCredit,
bool deploy,
uint value,
Token[] memory tokens,
bytes memory callData
) internal returns(bool) {
bool success = Precompile(ASYNC_CALL).precompileAsyncCall{value: value}(deploy, dst, refundTo, bounceTo, gas,
tokens, callData);
bool success = Precompile(ASYNC_CALL).precompileAsyncCall{value: value}(deploy, FORWARD_NONE, dst, refundTo,
bounceTo, feeCredit, tokens, callData);
return success;
}

Expand All @@ -68,8 +112,8 @@ library Nil {
function sendMessage(uint g, bytes memory message) internal {
uint message_size = message.length;
assembly {
// Call precompiled contract.
// Arguments: gas, precompiled address, value, input, input size, output, output size
// Call precompiled contract.
// Arguments: gas, precompiled address, value, input, input size, output, output size
if iszero(call(g, SEND_MESSAGE, 0, add(message, 32), message_size, 0, 0)) {
revert(0, 0)
}
Expand Down Expand Up @@ -116,22 +160,58 @@ library Nil {
function msgTokens() internal returns(Token[] memory) {
return Precompile(GET_MESSAGE_TOKENS).precompileGetMessageTokens();
}

// getShardId returns shard id for a given address.
function getShardId(address addr) internal pure returns(uint256) {
return uint256(uint160(addr)) >> (18 * 8);
}

// getGasPrice returns gas price for the shard, in which the given address is resided.
// It may return the price with some delay, i.e it can be not equal to the actual price. So, one should calculate
// real gas price pessimistically, i.e. `gas_price = getGasPrice() + blocks_delay * price_growth_factor`.
// Where, `blocks_delay` is the blocks number between the block for which gas price is actual and the block in which
// the message will be processed; and `price_growth_factor` is the maximum value by which gas can grow per block.
// TODO: add `getEstimatedGasPrice` method, which implements the above formula.
function getGasPrice(address addr) internal returns(uint256) {
return Precompile(GET_GAS_PRICE).precompileGetGasPrice(getShardId(addr));
}

function createAddress(uint shardId, bytes memory code, uint256 salt) internal returns(address) {
require(shardId < 0xffff, "Shard id is too big");
uint160 addr = uint160(uint256(getPoseidonHash(abi.encodePacked(code, salt))));
addr &= 0xffffffffffffffffffffffffffffffffffff;
addr |= uint160(shardId) << (18 * 8);
return address(addr);
}

function createAddress2(uint shardId, address sender, uint256 salt, uint256 codeHash) internal returns(address) {
require(shardId < 0xffff, "Shard id is too big");
uint160 addr = uint160(uint256(getPoseidonHash(abi.encodePacked(bytes1(0xff), sender, salt, codeHash))));
addr &= 0xffffffffffffffffffffffffffffffffffff;
addr |= uint160(shardId) << (18 * 8);
return address(addr);
}

function getPoseidonHash(bytes memory data) internal returns(uint256) {
return Precompile(GET_POSEIDON_HASH).precompileGetPoseidonHash(data);
}
}

// NilBase is a base contract that provides modifiers for checking the type of message (internal or external).
contract NilBase {
// Check that method was invoked from internal message
// onlyInternal checks that method was invoked from internal message.
modifier onlyInternal() {
require(isInternalMessage(), "Trying to call internal function with external message");
_;
}

// Check that method was invoked from external message
// onlyExternal checks that method was invoked from external message.
modifier onlyExternal() {
require(!isInternalMessage(), "Trying to call external function with internal message");
_;
}

// isInternalMessage returns true if the current message is internal.
function isInternalMessage() internal view returns (bool) {
bytes memory data;
(bool success, bytes memory returnData) = Nil.IS_INTERNAL_MESSAGE.staticcall(data);
Expand All @@ -146,9 +226,11 @@ contract NilBase {
contract Precompile {
function precompileMintCurrency(uint256 id, uint256 amount) public returns(bool) {}
function precompileGetCurrencyBalance(uint256 id, address addr) public returns(uint256) {}
function precompileAsyncCall(bool, address, address, address, uint, Nil.Token[] memory, bytes memory) public payable returns(bool) {}
function precompileAsyncCall(bool, uint8, address, address, address, uint, Nil.Token[] memory, bytes memory) public payable returns(bool) {}
function precompileSendTokens(address, Nil.Token[] memory) public returns(bool) {}
function precompileGetMessageTokens() public returns(Nil.Token[] memory) {}
function precompileGetGasPrice(uint id) public returns(uint256) {}
function precompileGetPoseidonHash(bytes memory data) public returns(uint256) {}
}

abstract contract NilBounceable is NilBase {
Expand Down
Loading

0 comments on commit 0f9ad23

Please sign in to comment.