Skip to content

Commit

Permalink
Merge pull request #253 from hyperledger-labs/custom-errors
Browse files Browse the repository at this point in the history
Use custom errors instead of string error

Signed-off-by: Jun Kimura <[email protected]>
  • Loading branch information
bluele authored Feb 22, 2024
2 parents 6d2ed66 + bce4aa9 commit 87f6b5a
Show file tree
Hide file tree
Showing 54 changed files with 3,186 additions and 1,107 deletions.
90 changes: 45 additions & 45 deletions .gas-snapshot
Original file line number Diff line number Diff line change
@@ -1,53 +1,53 @@
IBCMockAppTest:testHandshake() (gas: 3436712)
IBCMockAppTest:testHandshakeBetweenDifferentPorts() (gas: 2543821)
IBCMockAppTest:testPacketRelay() (gas: 9628786)
IBCMockAppTest:testPacketTimeout() (gas: 2924924)
IBCTest:testBenchmarkCreateMockClient() (gas: 209341)
IBCTest:testBenchmarkLCUpdateMockClient() (gas: 39935)
IBCTest:testBenchmarkRecvPacket() (gas: 137264)
IBCTest:testBenchmarkSendPacket() (gas: 85111)
IBCTest:testBenchmarkUpdateMockClient() (gas: 137315)
IBCMockAppTest:testHandshake() (gas: 3435974)
IBCMockAppTest:testHandshakeBetweenDifferentPorts() (gas: 2542936)
IBCMockAppTest:testPacketRelay() (gas: 9616141)
IBCMockAppTest:testPacketTimeout() (gas: 2924777)
IBCTest:testBenchmarkCreateMockClient() (gas: 209339)
IBCTest:testBenchmarkLCUpdateMockClient() (gas: 39945)
IBCTest:testBenchmarkRecvPacket() (gas: 134536)
IBCTest:testBenchmarkSendPacket() (gas: 85126)
IBCTest:testBenchmarkUpdateMockClient() (gas: 137373)
IBCTest:testToUint128((uint64,uint64)) (runs: 256, μ: 947, ~: 947)
TestICS02:testCreateClient() (gas: 24730617)
TestICS02:testInvalidCreateClient() (gas: 24578266)
TestICS02:testInvalidUpdateClient() (gas: 24581452)
TestICS02:testRegisterClient() (gas: 24325238)
TestICS02:testRegisterClientDuplicatedClientType() (gas: 24307288)
TestICS02:testRegisterClientInvalidClientType() (gas: 24292160)
TestICS02:testUpdateClient() (gas: 24747541)
TestICS03Handshake:testConnOpenAck() (gas: 1631286)
TestICS03Handshake:testConnOpenConfirm() (gas: 1765699)
TestICS03Handshake:testConnOpenInit() (gas: 1279049)
TestICS03Handshake:testConnOpenTry() (gas: 2160427)
TestICS03Handshake:testInvalidConnOpenAck() (gas: 1943429)
TestICS03Handshake:testInvalidConnOpenConfirm() (gas: 2007619)
TestICS03Handshake:testInvalidConnOpenInit() (gas: 666461)
TestICS03Handshake:testInvalidConnOpenTry() (gas: 2073805)
TestICS02:testCreateClient() (gas: 23647978)
TestICS02:testInvalidCreateClient() (gas: 23497549)
TestICS02:testInvalidUpdateClient() (gas: 23499468)
TestICS02:testRegisterClient() (gas: 23242497)
TestICS02:testRegisterClientDuplicatedClientType() (gas: 23225722)
TestICS02:testRegisterClientInvalidClientType() (gas: 23209851)
TestICS02:testUpdateClient() (gas: 23665020)
TestICS03Handshake:testConnOpenAck() (gas: 1631316)
TestICS03Handshake:testConnOpenConfirm() (gas: 1765734)
TestICS03Handshake:testConnOpenInit() (gas: 1279055)
TestICS03Handshake:testConnOpenTry() (gas: 2160463)
TestICS03Handshake:testInvalidConnOpenAck() (gas: 2031008)
TestICS03Handshake:testInvalidConnOpenConfirm() (gas: 2095567)
TestICS03Handshake:testInvalidConnOpenInit() (gas: 666816)
TestICS03Handshake:testInvalidConnOpenTry() (gas: 2101014)
TestICS03Version:testCopyVersions() (gas: 558658)
TestICS03Version:testFindSupportedVersion() (gas: 19400)
TestICS03Version:testIsSupportedVersion() (gas: 7864)
TestICS03Version:testPickVersion() (gas: 25399)
TestICS03Version:testPickVersion() (gas: 25327)
TestICS03Version:testVerifyProposedVersion() (gas: 11777)
TestICS03Version:testVerifySupportedFeature() (gas: 4153)
TestICS04Handshake:testBindPort() (gas: 40333)
TestICS04Handshake:testChanClose() (gas: 8475602)
TestICS04Handshake:testChanOpenAck() (gas: 2882165)
TestICS04Handshake:testChanOpenConfirm() (gas: 3062397)
TestICS04Handshake:testChanOpenInit() (gas: 2197364)
TestICS04Handshake:testChanOpenTry() (gas: 2655756)
TestICS04Handshake:testInvalidChanOpenAck() (gas: 2059642)
TestICS04Handshake:testInvalidChanOpenConfirm() (gas: 2117621)
TestICS04Handshake:testInvalidChanOpenInit() (gas: 1272924)
TestICS04Handshake:testInvalidChanOpenTry() (gas: 1348072)
TestICS04Packet:testAcknowledgementPacket() (gas: 2283020)
TestICS04Packet:testInvalidSendPacket() (gas: 2294517)
TestICS04Packet:testRecvPacket() (gas: 7604920)
TestICS04Packet:testRecvPacketTimeoutHeight() (gas: 2320237)
TestICS04Packet:testRecvPacketTimeoutTimestamp() (gas: 2318872)
TestICS04Packet:testSendPacket() (gas: 5093797)
TestICS04Packet:testTimeoutOnClose() (gas: 2507647)
TestICS20:testAddressToHex(address) (runs: 256, μ: 22713, ~: 22824)
TestICS04Handshake:testBindPort() (gas: 38248)
TestICS04Handshake:testChanClose() (gas: 8477010)
TestICS04Handshake:testChanOpenAck() (gas: 2881785)
TestICS04Handshake:testChanOpenConfirm() (gas: 3061565)
TestICS04Handshake:testChanOpenInit() (gas: 2197331)
TestICS04Handshake:testChanOpenTry() (gas: 2655828)
TestICS04Handshake:testInvalidChanOpenAck() (gas: 2061616)
TestICS04Handshake:testInvalidChanOpenConfirm() (gas: 2117572)
TestICS04Handshake:testInvalidChanOpenInit() (gas: 1275175)
TestICS04Handshake:testInvalidChanOpenTry() (gas: 1351818)
TestICS04Packet:testAcknowledgementPacket() (gas: 2283754)
TestICS04Packet:testInvalidSendPacket() (gas: 2288863)
TestICS04Packet:testRecvPacket() (gas: 7612057)
TestICS04Packet:testRecvPacketTimeoutHeight() (gas: 2316335)
TestICS04Packet:testRecvPacketTimeoutTimestamp() (gas: 2315328)
TestICS04Packet:testSendPacket() (gas: 5091415)
TestICS04Packet:testTimeoutOnClose() (gas: 2539103)
TestICS20:testAddressToHex(address) (runs: 256, μ: 22676, ~: 22804)
TestICS20:testHexToAddress(string) (runs: 256, μ: 4776, ~: 4734)
TestICS20:testIsEscapedString() (gas: 48979)
TestICS20:testMarshaling() (gas: 148145)
TestICS20:testParseAmount(uint256) (runs: 256, μ: 27316, ~: 23633)
TestICS20:testMarshaling() (gas: 148517)
TestICS20:testParseAmount(uint256) (runs: 256, μ: 27026, ~: 21930)
79 changes: 49 additions & 30 deletions contracts/apps/20-transfer/ICS20Bank.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,76 +7,95 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {ICS20Lib} from "./ICS20Lib.sol";
import {IICS20Bank} from "./IICS20Bank.sol";
import {IICS20Errors} from "./IICS20Errors.sol";

contract ICS20Bank is Context, AccessControl, IICS20Bank {
contract ICS20Bank is Context, AccessControl, IICS20Bank, IICS20Errors {
using Address for address;

bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");

// Mapping from token ID to account balances
mapping(string => mapping(address => uint256)) private _balances;
mapping(string => mapping(address => uint256)) internal _balances;

constructor() {
_setupRole(ADMIN_ROLE, _msgSender());
_grantRole(ADMIN_ROLE, _msgSender());
}

function setOperator(address operator) public virtual {
require(hasRole(ADMIN_ROLE, _msgSender()), "must have admin role to set new operator");
_setupRole(OPERATOR_ROLE, operator);
if (!hasRole(ADMIN_ROLE, _msgSender())) {
revert ICS20BankNotAdminRole(_msgSender());
}
_grantRole(OPERATOR_ROLE, operator);
}

function balanceOf(address account, string calldata denom) external view virtual returns (uint256) {
require(account != address(0), "ICS20Bank: balance query for the zero address");
function balanceOf(address account, string calldata denom) public view virtual returns (uint256) {
return _balances[denom][account];
}

function transferFrom(address from, address to, string calldata denom, uint256 amount) external virtual override {
require(to != address(0), "ICS20Bank: transfer to the zero address");
require(
from == _msgSender() || hasRole(OPERATOR_ROLE, _msgSender()), "ICS20Bank: caller is not owner nor approved"
);
function transferFrom(address from, address to, string calldata denom, uint256 amount) public virtual override {
if (to == address(0)) {
revert ICS20InvalidReceiver(to);
} else if (from != _msgSender() && !hasRole(OPERATOR_ROLE, _msgSender())) {
revert ICS20InvalidSender(from);
}
uint256 fromBalance = _balances[denom][from];
require(fromBalance >= amount, "ICS20Bank: insufficient balance for transfer");
if (fromBalance < amount) {
revert ICS20InsufficientBalance(from, fromBalance, amount);
}
unchecked {
_balances[denom][from] = fromBalance - amount;
}
_balances[denom][to] += amount;
}

function mint(address account, string calldata denom, uint256 amount) external virtual override {
require(hasRole(OPERATOR_ROLE, _msgSender()), "ICS20Bank: must have minter role to mint");
function mint(address account, string calldata denom, uint256 amount) public virtual override {
if (!hasRole(OPERATOR_ROLE, _msgSender())) {
revert ICS20BankNotMintRole(_msgSender());
}
_mint(account, denom, amount);
}

function burn(address account, string calldata denom, uint256 amount) external virtual override {
require(hasRole(OPERATOR_ROLE, _msgSender()), "ICS20Bank: must have minter role to mint");
function burn(address account, string calldata denom, uint256 amount) public virtual override {
if (!hasRole(OPERATOR_ROLE, _msgSender())) {
revert ICS20BankNotBurnRole(_msgSender());
}
_burn(account, denom, amount);
}

function addressToDenom(address tokenContract) public pure virtual override returns (string memory) {
return ICS20Lib.addressToHexString(tokenContract);
}

function deposit(address tokenContract, uint256 amount, address receiver) external virtual override {
require(tokenContract.isContract());
require(IERC20(tokenContract).transferFrom(_msgSender(), address(this), amount));
function deposit(address tokenContract, uint256 amount, address receiver) public virtual override {
if (tokenContract == address(0)) {
revert ICS20InvalidTokenContract(tokenContract);
}
if (!IERC20(tokenContract).transferFrom(_msgSender(), address(this), amount)) {
revert ICS20FailedERC20Transfer(tokenContract, _msgSender(), address(this), amount);
}
_mint(receiver, addressToDenom(tokenContract), amount);
}

function withdraw(address tokenContract, uint256 amount, address receiver) external virtual override {
require(tokenContract.isContract());
function withdraw(address tokenContract, uint256 amount, address receiver) public virtual override {
if (tokenContract == address(0)) {
revert ICS20InvalidTokenContract(tokenContract);
}
_burn(_msgSender(), addressToDenom(tokenContract), amount);
require(IERC20(tokenContract).transfer(receiver, amount));
if (!IERC20(tokenContract).transfer(receiver, amount)) {
revert ICS20FailedERC20TransferFrom(tokenContract, _msgSender(), address(this), receiver, amount);
}
}

function _mint(address account, string memory denom, uint256 amount) internal virtual {
function addressToDenom(address tokenContract) public pure virtual override returns (string memory) {
return ICS20Lib.addressToHexString(tokenContract);
}

function _mint(address account, string memory denom, uint256 amount) internal {
_balances[denom][account] += amount;
}

function _burn(address account, string memory denom, uint256 amount) internal virtual {
function _burn(address account, string memory denom, uint256 amount) internal {
uint256 accountBalance = _balances[denom][account];
require(accountBalance >= amount, "ICS20Bank: burn amount exceeds balance");
if (accountBalance < amount) {
revert ICS20InsufficientBalance(account, accountBalance, amount);
}
unchecked {
_balances[denom][account] = accountBalance - amount;
}
Expand Down
59 changes: 37 additions & 22 deletions contracts/apps/20-transfer/ICS20Lib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity ^0.8.20;

import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
import {IICS20Errors} from "./IICS20Errors.sol";

library ICS20Lib {
/**
Expand All @@ -15,9 +16,9 @@ library ICS20Lib {
string memo;
}

bytes public constant SUCCESSFUL_ACKNOWLEDGEMENT_JSON = bytes('{"result":"AQ=="}');
bytes public constant FAILED_ACKNOWLEDGEMENT_JSON = bytes('{"error":"failed"}');
bytes32 public constant KECCAK256_SUCCESSFUL_ACKNOWLEDGEMENT_JSON = keccak256(SUCCESSFUL_ACKNOWLEDGEMENT_JSON);
bytes internal constant SUCCESSFUL_ACKNOWLEDGEMENT_JSON = bytes('{"result":"AQ=="}');
bytes internal constant FAILED_ACKNOWLEDGEMENT_JSON = bytes('{"error":"failed"}');
bytes32 internal constant KECCAK256_SUCCESSFUL_ACKNOWLEDGEMENT_JSON = keccak256(SUCCESSFUL_ACKNOWLEDGEMENT_JSON);

uint256 private constant CHAR_DOUBLE_QUOTE = 0x22;
uint256 private constant CHAR_SLASH = 0x2f;
Expand Down Expand Up @@ -99,24 +100,35 @@ library ICS20Lib {
uint256 pos = 0;

unchecked {
require(bytes32(bz[pos:pos + 11]) == bytes32('{"amount":"'), "amount");
if (bytes32(bz[pos:pos + 11]) != bytes32('{"amount":"')) {
revert IICS20Errors.ICS20JSONUnexpectedBytes(pos, bytes32('{"amount":"'), bytes32(bz[pos:pos + 11]));
}
(pd.amount, pos) = parseUint256String(bz, pos + 11);

require(bytes32(bz[pos:pos + 10]) == bytes32(',"denom":"'), "denom");
if (bytes32(bz[pos:pos + 10]) != bytes32(',"denom":"')) {
revert IICS20Errors.ICS20JSONUnexpectedBytes(pos, bytes32(',"denom":"'), bytes32(bz[pos:pos + 10]));
}
(pd.denom, pos) = parseString(bz, pos + 10);

if (uint256(uint8(bz[pos + 2])) == CHAR_M) {
require(bytes32(bz[pos:pos + 9]) == bytes32(',"memo":"'), "memo");
if (bytes32(bz[pos:pos + 9]) != bytes32(',"memo":"')) {
revert IICS20Errors.ICS20JSONUnexpectedBytes(pos, bytes32(',"memo":"'), bytes32(bz[pos:pos + 9]));
}
(pd.memo, pos) = parseString(bz, pos + 9);
}

require(bytes32(bz[pos:pos + 13]) == bytes32(',"receiver":"'), "receiver");
if (bytes32(bz[pos:pos + 13]) != bytes32(',"receiver":"')) {
revert IICS20Errors.ICS20JSONUnexpectedBytes(pos, bytes32(',"receiver":"'), bytes32(bz[pos:pos + 13]));
}
(pd.receiver, pos) = parseString(bz, pos + 13);

require(bytes32(bz[pos:pos + 11]) == bytes32(',"sender":"'), "sender");
if (bytes32(bz[pos:pos + 11]) != bytes32(',"sender":"')) {
revert IICS20Errors.ICS20JSONUnexpectedBytes(pos, bytes32(',"sender":"'), bytes32(bz[pos:pos + 11]));
}
(pd.sender, pos) = parseString(bz, pos + 11);

require(pos == bz.length - 1 && uint256(uint8(bz[pos])) == CHAR_CLOSING_BRACE, "closing brace");
if (pos != bz.length - 1 || uint256(uint8(bz[pos])) != CHAR_CLOSING_BRACE) {
revert IICS20Errors.ICS20JSONClosingBraceNotFound(pos, bz[pos]);
}
}

return pd;
Expand All @@ -135,7 +147,9 @@ library ICS20Lib {
}
ret = ret * 10 + (c - 48);
}
require(pos < bz.length && uint256(uint8(bz[pos])) == CHAR_DOUBLE_QUOTE, "unterminated string");
if (pos >= bz.length || uint256(uint8(bz[pos])) != CHAR_DOUBLE_QUOTE) {
revert IICS20Errors.ICS20JSONStringClosingDoubleQuoteNotFound(pos, bz[pos]);
}
return (ret, pos + 1);
}
}
Expand All @@ -152,15 +166,16 @@ library ICS20Lib {
} else if (c == CHAR_BACKSLASH && i + 1 < bz.length) {
i++;
c = uint256(uint8(bz[i]));
require(
c == CHAR_DOUBLE_QUOTE || c == CHAR_SLASH || c == CHAR_BACKSLASH || c == CHAR_F || c == CHAR_R
|| c == CHAR_N || c == CHAR_B || c == CHAR_T,
"invalid escape"
);
if (
c != CHAR_DOUBLE_QUOTE && c != CHAR_SLASH && c != CHAR_BACKSLASH && c != CHAR_F && c != CHAR_R
&& c != CHAR_N && c != CHAR_B && c != CHAR_T
) {
revert IICS20Errors.ICS20JSONInvalidEscape(i, bz[i]);
}
}
}
}
revert("unterminated string");
revert IICS20Errors.ICS20JSONStringUnclosed(bz, pos);
}

function isEscapedJSONString(string calldata s) internal pure returns (bool) {
Expand Down Expand Up @@ -211,9 +226,6 @@ library ICS20Lib {
localValue >>= 4;
}
}
if (localValue != 0) {
revert("insufficient hex length");
}
return string(buffer);
}

Expand Down Expand Up @@ -250,8 +262,11 @@ library ICS20Lib {
* This is a copy from https://github.com/GNSPS/solidity-bytes-utils/blob/v0.8.0/contracts/BytesLib.sol
*/
function slice(bytes memory _bytes, uint256 _start, uint256 _length) internal pure returns (bytes memory) {
require(_length + 31 >= _length, "slice_overflow");
require(_bytes.length >= _start + _length, "slice_outOfBounds");
if (_length + 31 < _length) {
revert IICS20Errors.ICS20BytesSliceOverflow(_length);
} else if (_start + _length > _bytes.length) {
revert IICS20Errors.ICS20BytesSliceOutOfBounds(_bytes.length, _start, _start + _length);
}

bytes memory tempBytes;

Expand Down
Loading

0 comments on commit 87f6b5a

Please sign in to comment.