Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transfer-and-Call, Transfer-Multi-and-Call, Get-Call-Data and other enhancements #12

Merged
merged 9 commits into from
Aug 30, 2024
2 changes: 1 addition & 1 deletion .solhint.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"compiler-version": ["error", ">=0.8.12"],
"func-name-mixedcase": "off",
"func-visibility": ["error", { "ignoreConstructors": true }],
"max-line-length": ["error", 120],
"max-line-length": ["error", 123],
"named-parameters-mapping": "warn",
"no-console": "off",
"not-rely-on-time": "off",
Expand Down
58 changes: 29 additions & 29 deletions foundry.toml
Original file line number Diff line number Diff line change
@@ -1,43 +1,43 @@
# Full reference https://github.com/foundry-rs/foundry/tree/master/crates/config

[profile.default]
auto_detect_solc = false
block_timestamp = 1717200000 # June 1, 2024 at 00:00 GMT
bytecode_hash = "none"
evm_version = "shanghai"
fuzz = { runs = 1_000 }
gas_reports = ["SRF20"]
optimizer = true
optimizer_runs = 10_000
out = "out"
script = "script"
solc = "0.8.26"
src = "src"
test = "test"
auto_detect_solc = false
block_timestamp = 1717200000 # June 1, 2024 at 00:00 GMT
bytecode_hash = "none"
evm_version = "shanghai"
fuzz = { runs = 1_000 }
gas_reports = ["SRF20"]
optimizer = true
optimizer_runs = 10_000
out = "out"
script = "script"
solc = "0.8.26"
src = "src"
test = "test"

[profile.ci]
fuzz = { runs = 10_000 }
verbosity = 4
fuzz = { runs = 10_000 }
verbosity = 4

# Speed up compilation and tests during development
[profile.lite]
optimizer = false
optimizer = false

[doc]
ignore = ["**/*.t.sol"]
out = "docs"
repository = "https://github.com/sablier-labs/stdlib"
ignore = ["**/*.t.sol"]
out = "docs"
repository = "https://github.com/sablier-labs/stdlib"

[fmt]
bracket_spacing = true
int_types = "long"
line_length = 120
multiline_func_header = "all"
number_underscore = "thousands"
quote_style = "double"
tab_width = 4
wrap_comments = true
bracket_spacing = true
int_types = "long"
line_length = 120
multiline_func_header = "all"
number_underscore = "thousands"
quote_style = "double"
tab_width = 4
wrap_comments = true

[rpc_endpoints]
localhost = "http://localhost:8545"
sepolia = "https://sepolia.infura.io/v3/${API_KEY_INFURA}"
localhost = "http://localhost:8545"
sepolia = "https://sepolia.infura.io/v3/${API_KEY_INFURA}"
4 changes: 1 addition & 3 deletions src/Constants.sol
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.12;

// import { SubID } from "./Types.sol";

/*//////////////////////////////////////////////////////////////////////////
NATIVE TOKENS
//////////////////////////////////////////////////////////////////////////*/

bytes32 constant DEFAULT_SUB_ID = bytes32(0);
uint256 constant DEFAULT_SUB_ID = 0;

/*//////////////////////////////////////////////////////////////////////////
PRECOMPILES
Expand Down
6 changes: 0 additions & 6 deletions src/Types.sol

This file was deleted.

29 changes: 10 additions & 19 deletions src/precompiles/native-tokens/INativeTokens.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,23 @@ pragma solidity >=0.8.12;
/// @dev It is NOT recommended to use this interface directly. Instead, users should use the {NativeTokens} library.
/// This interface is used by the library to ABI encode the precompile calls.
interface INativeTokens {
function balanceOf(uint256 tokenID, address account) external returns (uint256);
function burn(address holder, bytes32 subID, uint256 amount) external;
function mint(address recipient, bytes32 subID, uint256 amount) external;
function transfer(address from, address to, bytes32 tokenID, uint256 amount) external;
function balanceOf(address account, uint256 tokenID) external returns (uint256);
function burn(uint256 subID, address holder, uint256 amount) external;
function getCallValues() external returns (uint256[] memory, uint256[] memory);
function mint(uint256 subID, address recipient, uint256 amount) external;
function transfer(address to, uint256 tokenID, uint256 amount) external;
function transferAndCall(
address from,
address to,
bytes32 tokenID,
address recipientAndCallee,
uint256 tokenID,
uint256 amount,
address callee,
bytes calldata data
)
external;
function transferMultiple(
address from,
address[] calldata to,
bytes32[] calldata tokenIDs,
uint256[] calldata amounts
)
external;
function transferMultiple(address to, uint256[] calldata tokenIDs, uint256[] calldata amounts) external;
function transferMultipleAndCall(
address from,
address[] calldata to,
bytes32[] calldata tokenIDs,
address recipientAndCallee,
uint256[] calldata tokenIDs,
uint256[] calldata amounts,
address callee,
bytes calldata data
)
external;
Expand Down
112 changes: 58 additions & 54 deletions src/precompiles/native-tokens/NativeTokens.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ library NativeTokens {
//////////////////////////////////////////////////////////////////////////*/

/// @notice Returns the balance of the `account` for the native token `tokenID`.
/// @param tokenID The ID of the native token to query the balance of.
/// @param account The address to query the balance of.
/// @param tokenID The ID of the native token to query the balance of.
/// @return The balance of the `account` for the native token `tokenID`, denoted in 18 decimals.
function balanceOf(uint256 tokenID, address account) internal view returns (uint256) {
function balanceOf(address account, uint256 tokenID) internal view returns (uint256) {
// ABI encode the input parameters.
bytes memory precompileData = abi.encodeCall(INativeTokens.balanceOf, (tokenID, account));
bytes memory callData = abi.encodeCall(INativeTokens.balanceOf, (account, tokenID));

// Call the precompile.
(bool success, bytes memory returnData) = PRECOMPILE_NATIVE_TOKENS.staticcall(precompileData);
(bool success, bytes memory returnData) = PRECOMPILE_NATIVE_TOKENS.staticcall(callData);

// This is an unexpected error since the VM should have panicked if the call failed.
if (!success) {
Expand All @@ -34,39 +34,65 @@ library NativeTokens {
USER-FACING NON-CONSTANT FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/

/// @notice Burns `amount` tokens with the sub-identifier `sub_id` from the `holder`'s account.
/// @notice Burns `amount` tokens with the sub-identifier `subID` from the `holder`'s account.
/// @dev Generates a Burn receipt.
///
/// Requirements:
/// - The caller of this function must be a contract.
/// - The holder must have at least `amount` tokens.
///
/// @param holder The address to burn native tokens from.
/// @param subID The sub-identifier of the native token to burn.
/// @param amount The quantity of native tokens to burn.
function burn(address holder, bytes32 subID, uint256 amount) internal {
function burn(address holder, uint256 subID, uint256 amount) internal {
IaroslavMazur marked this conversation as resolved.
Show resolved Hide resolved
// ABI encode the input parameters.
bytes memory precompileData = abi.encodeCall(INativeTokens.burn, (holder, subID, amount));
bytes memory callData = abi.encodeCall(INativeTokens.burn, (subID, holder, amount));

// Call the precompile, ignoring the response since the VM will panic if there's an issue.
(bool response,) = PRECOMPILE_NATIVE_TOKENS.delegatecall(precompileData);
(bool response,) = PRECOMPILE_NATIVE_TOKENS.delegatecall(callData);
smol-ninja marked this conversation as resolved.
Show resolved Hide resolved
response;
}

/// @notice Returns the token ids and amounts of the Native Tokens transferred in the context of the current
/// contract call.
///
/// Requirements:
/// - The caller of this function must be a contract.
///
/// @return tokenIDs The IDs of the transferred Native Tokens.
/// @return amounts The amounts of the transferred Native Tokens.
function getCallValues(address /*notUsed*/ ) internal returns (uint256[] memory, uint256[] memory) {
// ABI encode the input parameters.
bytes memory callData = abi.encodeCall(INativeTokens.getCallValues, ());

// Call the precompile.
(bool success, bytes memory returnData) = PRECOMPILE_NATIVE_TOKENS.delegatecall(callData);

// This is an unexpected error since the VM should have panicked if the call failed.
if (!success) {
revert StdLib_UnknownError("NativeTokens: getCallValues failed");
}

// Decode the return data.
return abi.decode(returnData, (uint256[], uint256[]));
}

/// @notice Mints `amount` tokens with sub-identifier `subID` to the provided `recipient`.
/// @dev Generates a Mint receipt.
///
/// Requirements:
/// - The caller of this function must be a contract.
/// - The `recipient`'s balance must not overflow.
///
/// @param recipient The address to mint native tokens to.
/// @param subID The sub-identifier of the native token to mint.
/// @param amount The quantity of native tokens to mint.
function mint(address recipient, bytes32 subID, uint256 amount) internal {
function mint(address recipient, uint256 subID, uint256 amount) internal {
// ABI encode the input parameters.
bytes memory precompileData = abi.encodeCall(INativeTokens.mint, (recipient, subID, amount));
bytes memory callData = abi.encodeCall(INativeTokens.mint, (subID, recipient, amount));

// Call the precompile, ignoring the response since the VM will panic if there's an issue.
(bool response,) = PRECOMPILE_NATIVE_TOKENS.delegatecall(precompileData);
(bool response,) = PRECOMPILE_NATIVE_TOKENS.delegatecall(callData);
response;
}

Expand All @@ -81,15 +107,12 @@ library NativeTokens {
/// @param to The address of the recipient.
/// @param tokenID The ID of the native token to transfer.
/// @param amount The quantity of native tokens to transfer.
function transfer(address to, bytes32 tokenID, uint256 amount) internal {
// The address in the calling contract.
address from = address(this);

function transfer(address to, uint256 tokenID, uint256 amount) internal {
// ABI encode the input parameters.
bytes memory precompileData = abi.encodeCall(INativeTokens.transfer, (from, to, tokenID, amount));
bytes memory callData = abi.encodeCall(INativeTokens.transfer, (to, tokenID, amount));

// Call the precompile, ignoring the response because the VM will panic if there's an issue.
(bool response,) = PRECOMPILE_NATIVE_TOKENS.delegatecall(precompileData);
(bool response,) = PRECOMPILE_NATIVE_TOKENS.delegatecall(callData);
response;
}

Expand All @@ -100,90 +123,71 @@ library NativeTokens {
/// Requirements:
/// - The calling contract must have at least `amount` tokens.
///
/// @param to The address of the recipient.
/// @param recipientAndCallee The address of the contract recipient of the tokens which will, also, be called.
/// @param tokenID The sub-identifier of the native token to transfer.
/// @param amount The quantity of native tokens to transfer.
/// @param callee The address of the contract to call after the transfer.
/// @param data The call data to pass to the `callee`.
function transferAndCall(
address to,
bytes32 tokenID,
address recipientAndCallee,
uint256 tokenID,
uint256 amount,
address callee,
bytes calldata data
)
internal
{
// The address in the calling contract.
address from = address(this);

// ABI encode the input parameters.
bytes memory precompileData =
abi.encodeCall(INativeTokens.transferAndCall, (from, to, tokenID, amount, callee, data));
bytes memory callData =
abi.encodeCall(INativeTokens.transferAndCall, (recipientAndCallee, tokenID, amount, data));

// Call the precompile, ignoring the response because the VM will panic if there's an issue.
(bool response,) = PRECOMPILE_NATIVE_TOKENS.delegatecall(precompileData);
(bool response,) = PRECOMPILE_NATIVE_TOKENS.delegatecall(callData);
response;
}

/// @notice Performs multiple native token transfers from the calling contract to the recipients `to`.
/// @notice Performs multiple native token transfers from the calling contract to the recipient `to`.
/// @dev In SabVM, contracts cannot transfer native tokens on behalf of other addresses.
/// Generates multiple Transfer receipts.
///
/// Requirements:
/// - The calling contract must have at least `amounts[i]` tokens for each token ID `tokenIDs[i]`.
///
/// @param to The addresses of the recipients.
/// @param to The address of the recipient.
/// @param tokenIDs The IDs of the native tokens to transfer.
/// @param amounts The quantities of native tokens to transfer.
function transferMultiple(
address[] calldata to,
bytes32[] calldata tokenIDs,
uint256[] calldata amounts
)
internal
{
// The current address in the calling contract.
address from = address(this);

function transferMultiple(address to, uint256[] calldata tokenIDs, uint256[] calldata amounts) internal {
// ABI encode the input parameters.
bytes memory precompileData = abi.encodeCall(INativeTokens.transferMultiple, (from, to, tokenIDs, amounts));
bytes memory callData = abi.encodeCall(INativeTokens.transferMultiple, (to, tokenIDs, amounts));

// Call the precompile, ignoring the response because the VM will panic if there's an issue.
(bool response,) = PRECOMPILE_NATIVE_TOKENS.delegatecall(precompileData);
(bool response,) = PRECOMPILE_NATIVE_TOKENS.delegatecall(callData);
response;
}

/// @notice Performs multiple native token transfers from the calling contract to the recipients `to`, and calls the
/// @notice Performs multiple native token transfers from the calling contract to the recipient `to`, and calls the
/// `callee` with the calldata `data`.
/// @dev Generates multiple Transfer receipts.
///
/// Requirements:
/// - The calling contract must have at least `amounts[i]` tokens for each token ID `tokenIDs[i]`.
///
/// @param to The addresses of the recipients.
/// @param recipientAndCallee The address of the contract recipient of the tokens which will, also, be called.
/// @param tokenIDs The IDs of the native tokens to transfer.
/// @param amounts The quantities of native tokens to transfer.
/// @param callee The address of the contract to call after the transfer.
/// @param data The call data to pass to the `callee`.
function transferMultipleAndCall(
address[] calldata to,
bytes32[] calldata tokenIDs,
address recipientAndCallee,
uint256[] calldata tokenIDs,
uint256[] calldata amounts,
address callee,
bytes calldata data
)
internal
{
// The address in the calling contract.
address from = address(this);

// ABI encode the input parameters.
bytes memory precompileData =
abi.encodeCall(INativeTokens.transferMultipleAndCall, (from, to, tokenIDs, amounts, callee, data));
bytes memory callData =
abi.encodeCall(INativeTokens.transferMultipleAndCall, (recipientAndCallee, tokenIDs, amounts, data));

// Call the precompile, ignoring the response because the VM will panic if there's an issue.
(bool response,) = PRECOMPILE_NATIVE_TOKENS.delegatecall(precompileData);
(bool response,) = PRECOMPILE_NATIVE_TOKENS.delegatecall(callData);
response;
}
}
8 changes: 4 additions & 4 deletions src/standards/srf-20/SRF20Mock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ pragma solidity >=0.8.12;

import { SRF20 } from "../../standards/srf-20/SRF20.sol";

/// @notice Dummy mock contract for testing the SRF-20 implementation. The difference between this
/// and {SRF20} is that this mock can be deployed. Abstracts cannot be deployed.
/// @notice Dummy mock contract for testing the SRF-20 implementation. The difference between this and {SRF20} is that
/// this mock can be deployed. Abstracts cannot be deployed.
/// @dev WARNING: This contract is for testing purposes only. Do not use in production.
contract SRF20Mock is SRF20 {
constructor(string memory name, string memory symbol) SRF20(name, symbol) { }

function burn(address holder, uint256 amount) external {
_burn(holder, amount);
function burn(uint256 amount) external {
_burn(msg.sender, amount);
}

function mint(address recipient, uint256 amount) external {
Expand Down
Loading