無知

갈 길이 먼 공부 일기

기술 공부/블록체인

스마트 컨트랙트 (5-7) | Solidity(솔리디티) 라이브러리, Libraries

moozii 2022. 4. 18. 23:31

 

Photo By Nenad Novaković @dvlden in Unsplash

 

1. Libraries

 

- 솔리디티에서 라이브러리는 한번 설치되어 여러 다른 contract들에 의해서 사용.

- 따라서, 동일한 코드가 반복해서 이더리움 네트워크에 설치되는 것을 방지.

- 라이브러리 함수는 contract 메소드 호출과 유사하다

 

- 라이브러리는 자신의 storage를 갖지 못한다

- 라이브러리는 이더를 소유할 수 없다

- 라이브러리는 state variable을 가질 수 없다

- 라이브러리는 상속 관계에 있을 수 없다

- 라이브러리는 fallback 함수를 가질 수 없다

- 라이브러리는 payable 함수를 가질 수 없다

 

pragma solidity >=0.4.0 <0.7.0

library lib {
	function getBal() public view returns (uint) {
    	return address(this);
    }
    
    function xor (bool a, bool b) public pure returns (bool) {
    	return (a || b) && !(a && b);
    }
}

contract Sample {
	function getBal_in_contract() public view returns (uint) {
    	return lib.getBal();
    }
    
    function xor_in_contract() public pure returns (bool){
    	return (lib.xor(true, false) == true);
    }
    
    function() external payable {}
}

contract Sample_2 {
	using lib for bool;
    
    function xor_in_contract() public pure returns (bool){
    	return (true.xor(false) == true);
    }
    
    function() external payable {}
}

 

 

Libraries are similar to contracts, but their purpose is that they are deployed only once at a specific address and their code is reused using the DELEGATECALL (CALLCODE until Homestead) feature of the EVM. This means that if library functions are called, their code is executed in the context of the calling contract, i.e. this points to the calling contract, and especially the storage from the calling contract can be accessed. As a library is an isolated piece of source code, it can only access state variables of the calling contract if they are explicitly supplied (it would have no way to name them, otherwise). Library functions can only be called directly (i.e. without the use of DELEGATECALL) if they do not modify the state (i.e. if they are view or pure functions), because libraries are assumed to be stateless. In particular, it is not possible to destroy a library.

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;


// We define a new struct datatype that will be used to
// hold its data in the calling contract.
struct Data {
    mapping(uint => bool) flags;
}

library Set {
    // Note that the first parameter is of type "storage
    // reference" and thus only its storage address and not
    // its contents is passed as part of the call.  This is a
    // special feature of library functions.  It is idiomatic
    // to call the first parameter `self`, if the function can
    // be seen as a method of that object.
    function insert(Data storage self, uint value)
        public
        returns (bool)
    {
        if (self.flags[value])
            return false; // already there
        self.flags[value] = true;
        return true;
    }

    function remove(Data storage self, uint value)
        public
        returns (bool)
    {
        if (!self.flags[value])
            return false; // not there
        self.flags[value] = false;
        return true;
    }

    function contains(Data storage self, uint value)
        public
        view
        returns (bool)
    {
        return self.flags[value];
    }
}


contract C {
    Data knownValues;

    function register(uint value) public {
        // The library functions can be called without a
        // specific instance of the library, since the
        // "instance" will be the current contract.
        require(Set.insert(knownValues, value));
    }
    // In this contract, we can also directly access knownValues.flags, if we want.
}​


https://docs.soliditylang.org/en/v0.8.13/contracts.html#libraries 
https://docs.soliditylang.org/en/v0.8.13/contracts.html#using-for 

 

 

 

 

 

2. Openzepplin

(1) 덧셈 연산 Overflow

더하기 연산에서 overflow가 발생할 수 있다. 엄청 큰 값에 더하기 1을 했는데 overflow가 발생하고 0을 반환할 수 있다. 
만약에 이 코드가 balance를 update 하는 코드라면 모든 잔고가 사라질 수도 있으므로 더하기 연산자를 사용할 때Overflow가 발생하는지를 확인해야 한다.

 

Overflow Rule for addition

If 2 Two's Complement numbers are added, and they both have the same sign (both positive or both negative), then overflow occurs if and only if the result has the opposite sign. Overflow never occurs when adding operands with different signs.

i.e. Adding two positive numbers must give a positive result
Adding two negative numbers must give a negative result

Overflow occurs if
(+A) + (+B) = −C(−A) + (−B) = +C

Example:
Using 4-bit Two's Complement numbers (−8 ≤ x ≤ +7)
  (−7) 1001
+(−6) 1010
------------
(−13) 1 0011 = 3 : Overflow (largest −ve number is −8)

https://www.doc.ic.ac.uk/~eedwards/compsys/arithmetic/index.html 

 

 

 

(2) Openzeppelin SafeMath

 

openzeppelin의 라이브러리 중 safeMath를 import 해서 safeMath 라이브러리의 add 명령을 사용해 덧셈을 구현해보면 오버플로우 케이스에서 문제 발생을 방지할 수 있다. 오버플로우가 일어난 결과를 그대로 저장하고 넘어가는 것이 아니라, 에러가 나고 이 메소드 호출로 생긴 transaction이 취소된다. 그리고 “additional overflow”라는 메시지를 출력한다.

 

pragma solidity >=0.4.0 <0.7.0
import 'https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/SafeMath.sol';

contract Sample {
	function test(uint256 a, uint256 b) public pure returns (uint256) {
    	return (SafeMath.add(a, b));
    }
}

contract Sample_2 {
	using SafeMath for *;
    
	function test(uint256 a, uint256 b) public pure returns (uint256) {
    	return (a.add(b));
    }
}

 

 

The most popular math related library OpenZeppelin Contracts provides is SafeMath, which provides mathematical functions that protect your contract from overflows and underflows. Include the contract with using SafeMath for uint256; and then call the functions:
myNumber.add(otherNumber)
myNumber.sub(otherNumber)
myNumber.div(otherNumber)
myNumber.mul(otherNumber)
myNumber.mod(otherNumber)

https://docs.openzeppelin.com/contracts/4.x/utilities#api:math.adoc#SafeMath 
 

Utilities - OpenZeppelin Docs

The most popular math related library OpenZeppelin Contracts provides is SafeMath, which provides mathematical functions that protect your contract from overflows and underflows. Include the contract with using SafeMath for uint256; and then call the funct

docs.openzeppelin.com

 

 

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/math/SafeMath.sol)

pragma solidity ^0.8.0;

// CAUTION
// This version of SafeMath should only be used with Solidity 0.8 or later,
// because it relies on the compiler's built in overflow checks.

/**
 * @dev Wrappers over Solidity's arithmetic operations.
 *
 * NOTE: `SafeMath` is generally not needed starting with Solidity 0.8, since the compiler
 * now has built in overflow checking.
 */
library SafeMath {
    /**
     * @dev Returns the addition of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            uint256 c = a + b;
            if (c < a) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b > a) return (false, 0);
            return (true, a - b);
        }
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
            // benefit is lost if 'b' is also tested.
            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
            if (a == 0) return (true, 0);
            uint256 c = a * b;
            if (c / a != b) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the division of two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a / b);
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a % b);
        }
    }

    /**
     * @dev Returns the addition of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     *
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        return a + b;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return a - b;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     *
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        return a * b;
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator.
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return a / b;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return a % b;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
     * overflow (when the result is negative).
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {trySub}.
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        unchecked {
            require(b <= a, errorMessage);
            return a - b;
        }
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting with custom message on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        unchecked {
            require(b > 0, errorMessage);
            return a / b;
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting with custom message when dividing by zero.
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {tryMod}.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        unchecked {
            require(b > 0, errorMessage);
            return a % b;
        }
    }
}
 

GitHub - OpenZeppelin/openzeppelin-contracts: OpenZeppelin Contracts is a library for secure smart contract development.

OpenZeppelin Contracts is a library for secure smart contract development. - GitHub - OpenZeppelin/openzeppelin-contracts: OpenZeppelin Contracts is a library for secure smart contract development.

github.com

 

 

 

 

[Learn More...]

https://medium.com/coinmonks/solidity-for-developers-libraries-in-solidity-f8c7e348dc24

 

Solidity For Developers: Libraries In Solidity

Understand The Fundamentals of Libraries In Solidity, And Learn To Make Your Own

medium.com