//기존
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
//ERC721.sol가 없고 이제 ERC721URIStorage.sol이거 씀
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
근데 나는 여전히 계속 빨간줄 뜨더라고 그래서
../node_modules/
로 경로 찾아주니까 빨간줄 사라짐
2. DeclarationError
사실 무엇보다
변수들을 못찾았는데
DeclarationError: Undeclared identifier. Did you mean "hash"? --> project:/contracts/UniqueAsset.sol:24:11: | 24 | require(hashes[hash] != 1); | ^^^^^^
,DeclarationError: Undeclared identifier. Did you mean "hash"? --> project:/contracts/UniqueAsset.sol:25:3: | 25 | hashes[hash] = 1; | ^^^^^^
그이유는!!!!
괄호를 잘못 묶었더라고..ㅋㅋㅋㅋ...
contract 안에 함수 있는건데
contract랑 함수를 따로 해서 contract에 담아둔 애들이름 못 쓰는 거였다
결과 컴파일 잘된다!
3. 경고 Visibility for constructor is ignored.
경고 뜨는 건
Warning: Visibility for constructor is ignored. If you want the contract to be non-deployable, making it "abstract" is sufficient.
(경고: 생성자의 가시성은 무시됩니다. 계약을 배포할 수 없도록 하려면"추상"으로 만드는 것으로 충분합니다.) --> project:/contracts/UniqueAsset.sol:16:4: | 16 | constructor() public ERC721("UniqueAsset", "UNA") {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
생성자에 더이상 가시성(public/external)이 필요하지 않다고 한다.
왜냐면 생성자는 처음에 배포됐을 때만 읽히니까 다음에 또 불려질 일이 없다.
public을 지웠더니 경고가 사라졌다.
추가적으로는
솔리디티 버전을 코드랑 잘 맞춰줘야하는 것. 필요할 때 f1눌러서 버전수정했다.
따라한 내용 정리
필요한 것
vscode(nodejs, 솔리디티확장자)
IPFS 설치
Ganache — 이더리움의 로컬 블록체인 — 설치
트러플 설치
NodeJS 설치
피나타 API 키
스마트 계약 작성
(오픈제플린에서 가져와서 쓸거임)
새프로젝트 폴더 만듦
mkdir mySpecialAsset
디랙토리 초기화
npm init -y
Truffle을 활용해 스마트 계약 프로젝트 초기화
truffle init
Open Zeppelin 계약에 액세스할거라서 설치
npm install @openzeppelin/contracts
contracts 폴더 구조 안에 파일 생성! (솔리디티 버전 맞춰서 하기)
UniqueAsset.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;
import "../node_modules/@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "../node_modules/@openzeppelin/contracts/utils/Counters.sol";
contract UniqueAsset is ERC721URIStorage{
//Counters to help us increment the identifiers for the tokens we mint.
// 카운터 라이브러리(갯수세는역할함)를 쓴다. 이 유형으로
using Counters for Counters.Counter;
//변수만듦to keep track of all of the tokens we’ve issued.
Counters.Counter private _tokenIds;
//mapping for the IPFS hashes associated with tokens.
mapping(string => uint8) hashes;
constructor() ERC721("UniqueAsset", "UNA") {}
// 특정IPFS해시에 아직 mint안되어있으면 mint하는 함수
// a method to our contract that will allow us to mint an NFT for a specific IPFS hash if not token has been minted yet for that hash
//nft수령인//NFT를 생성할 콘텐츠와 관련된 IPFS 해시 //자산에 대한 JSON 메타데이터에 대한 링크를 참조해야 한다
function awardItem(address recipient, string memory hash, string memory metadata) public returns (uint256)
{
require(hashes[hash] != 1);
hashes[hash] = 1;
_tokenIds.increment();
uint256 newItemId = _tokenIds.current();
_mint(recipient, newItemId);
_setTokenURI(newItemId, metadata);
return newItemId;
}
}
폴더구조 migrations 들어가서
2_deploy_contract.js
파일 생성
var UniqueAsset = artifacts.require("UniqueAsset");
module.exports = function (deployer) {
deployer.deploy(UniqueAsset);
};
다 하면 컴파일
truffle compile
만약 오류가 발생하면 Ganache가 실행 중인 포트를 수동으로 설정 (아니면 나처럼 버전, 모듈경로 등의 문제일수도 있음 위에 참조)
truffle-config.js
(가나슈 퀵스타 누르면 뜨는 창에있는거랑 동일하게)
truffle migrate
이걸로
NFT 스마트 계약을 배포
이제 스마트 계약을 처리했으므로 기본 자산을 IPFS로 가져와 관련 NFT를 발행할 때 사용할 수 있는지 확인
IPFS에 자산 추가
우리는 Pinata를 사용하여 자산을 IPFS에 추가하고 고정된 상태로 유지하도록 할 것입니다.
해당 해시는 자산의 검증 가능한 표현이고IPFS 네트워크의 자산을 의미한다.누군가가 자산을 변조하고 변경한 경우 해시가 달라진다. 이 해시는 우리의 스마트 계약을 통해 NFT를 발행할 때 사용해야 하는 것이다.공개 게이트웨이를 제공하는 모든 IPFS 호스트는 이제 콘텐츠를 표시할 수 있다.. 라고 위 링크 글에 있다.
이게 Pinata에 자산을 업로드 하는 거라는데 pinata 내 계정에는 뭔가 달라진게 안보인다.
팀원한테 물어보니 payable이 truffle버전 5이상부터 된다는 얘기를 해서 (하지만 관련문서를 찾아봐도 잘안나온다 솔리디티 버전에 관한 얘기는 있는데 "address payablenot supported in solc 0.4.x or earlier." 나는 solc버전이 0.8이었어서...)
npm uninstall -g truffle 하고
npm i g truffle@5.5.0
로 다시 까니까 됐다.
(packagelock json이 이런거 통일시켜주는 애려나? 근데 그거 있으면 npm i 모듈 깔때 에러 떠서 삭제하고 하는데 흠...)
enum 사람이 읽을 수 있게 , 사용자/개발자에 의해 정의된 상수세트 타입이다. (uint8=0~255(2^8-1))
enum 이름 {
}
contract lec38{
//자동차 상태 나타냄
enum CarStatus{
//0
TurnOff,
//1
TurnOn,
Driving,
Stop
}
//상태의 변수
CarStatus public carStatus;
constructor(){
// 이넘쓸때 이넘.안의 이름
carStatus = CarStatus.TurnOff;
}
event carCurrentStatus(CarStatus _carStatus, uint256 _carStatusInInt);
function turnOnCar() public {
//자동차에 시동꺼져있어야한다.
require(carStatus == CarStatus.TurnOff, "To turn on, your car must be turned off");
//CarStatus화로 나타내는 방법.
// require(carStatus == CarStatus(0), "To turn on, your car must be turned off");
//시동켜기
carStatus = CarStatus.TurnOn;
//carStatus = CarStatus(1);
//uint256으로 출력하려면 그렇게 형변환해야 출력할수잇음
emit carCurrentStatus(carStatus,uint256(carStatus));
}
//자동차 상태를 운전중으로 변경시켜주는 함수
function DrivingCar() public {
//자동차 시동켜져있어야함
require(carStatus == CarStatus.TurnOn, "To drive a car, your car must be turned on");
carStatus = CarStatus.Driving;
emit carCurrentStatus(carStatus,uint256(carStatus));
}
//자동차 멈추고 싶다
function StopCar() public {
require(carStatus == CarStatus.Driving, "To drive a car, your car must be turned on");
carStatus = CarStatus.Stop;
emit carCurrentStatus(carStatus,uint256(carStatus));
}
//시동끄기
function turnOffCar() public {
require(carStatus == CarStatus.TurnOn
|| carStatus == CarStatus.Stop , "To turn off, your car must be turned on or driving");
carStatus = CarStatus.TurnOff;
emit carCurrentStatus(carStatus,uint256(carStatus));
}
//자동자가 무슨상태인지 나타내는 함수
function CheckStatus() public view returns(CarStatus) {
return carStatus;
}
}
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 < 0.9.0;
//주소.balance
//주소의 현재 갖고 있는 이더의 잔액을 의미함. // msg.value는 송금액을 의미한다 달라!
// msg.sender
// msg.sender는 스마트컨트랙을 사용하는 주체라고 볼 수 있다
// 앞으로 설명할 call 과 delegate call에서 주요 내용이니 관심잇게 봐라
contract MobileBanking{
event SendInfo(address _msgSender, uint256 _currentValue);
event MyCurrentValue(address _msgSender,uint256 _value);
event CurrentValueOfSomeone(address _msgSender, address _to, uint256 _value);
//이더를 보낼라면
//을 넣을 줄 알았지만!!! 보내는 주소, 받을 사람 주소만 있음 되는구나.. 이더를 받아야되기때문에 payable써준다 //함수전체도 이더보는거라 payable써줌
function sendEther(address payable _to) public payable {
require (msg.sender.balance >= msg.value , "your balance is not enough");
//트랜스퍼 함수를 통해 이더 송금
_to.transfer(msg.value);
//보낸 나의 주소, 내 잔고
emit SendInfo(msg.sender, (msg.sender).balance);
}
//그냥 현재 잔고확인 함수인듯?
function checkValueNow() public{
emit MyCurrentValue(msg.sender, (msg.sender).balance);
}
//어떤 계정의 잔고 확인하고 싶을때, 확인하고 싶은 주소 넣기
function checkUserValue(address _to) public{
emit CurrentValueOfSomeone(msg.sender, _to, _to.balance);
}
}
이더송금하기
sendEther함수 사용
1. 코드를 컴파일한다
또는 우클릭해서 원하는 코드 컴파일.
2. ACCOUNT 에서 현재 내 계정주소 확인하고 Deploy 배포한다.
1로 표시한거 > 계정주소 넣을 부분 2로 표시한거 > 이더금액 넣는 부분 3로 표시한거 > Deploy 배포 4로 표시한거 > 함수사용하는 부분 5로 표시한거 > 내역 (숫자 이거 순서 아님주의!!!)
//2개의 원소를 담을 수 있는 고정길이의 배열;
uint[2] fixedArray;
//고정길이의 배열, 5개의 스트링을 담을 수 있다.
string[5] stringArray;
//동적배열은 고정된 크기가 없으며 계속 크기가 커질 수 있다.
uint[] dynamicArray;
구조체의 배열을 생성할 수 있다!
아까 우리가 sturct Person {uint age; string name;} 이렇게 만들었던 구조체를 이용하면
Person[] people; // 이는 동적 배열로, 원소를 계속 추가할 수 있다.
상태변수가 블록체인에 영구적으로 저장할 수 있는 것처럼 구조체의 동적배열을 생성하면
마치 데이터베이스처럼 컨트랙트에 구조화된 데이터를 저장하는데 유용하다.
Public 배열
public으로 배열을 선언할 수 있음 솔리디티는 이런 배열을 위해 getter메소드를 자동적으로 생성한다.
Person[] public people;
다른 컨트랙트들은 이 배열을 읽을 수는 있게되지만 쓸 수는 없다.
이는 컨트랙트에 공개데이터를 저장할 때 유용한 패턴이다.
지금까지 한 코드
pragma solidity ^0.4.19;
contract ZombieFactory {
uint dnaDigits = 16;
uint dnaModulus = 10 ** dnaDigits;
struct Zombie {
string name;
uint dna;
}
// 좀비들을 다른 앱에 자랑하고 싶어서 좀비 군대 저장소를public으로 함.
//Zombie구조체의 public배열을 생성하고 이름을 zombies로 한다.
Zombie[] public zombies;
}
챕터 7: 함수 선언
솔리디티에서 함수 선언
function eatHam(string _name, uint _amount){
}
(함수의 인자명을 _로 시작하는 건 전역변수랑 구별하려고 하는 관례때문임~)
이 함수를 오출 할때는 이렇게 할 수 있겠지
eatHam("jun",100);
챕터 8: 구조체와 배열 활용하기
새로운 구조체를 생성하기
전에 만들어 놓은 Person구조체를 사용할거야.
//구조체 Person 틀
struct Person {
uint age;
string name;
}
//Person배열을 peole로 이름지음
Person[] public people;
그 틀로 새로운 Person satoshi을 찍어낼거야.
//Person틀쓸거고 변수명은 satoshi임 // age, name
Person satoshi = Person(172,"Satoshi");
//people배열에 이 사람을 추가한다.
peole.push(satoshi);
이 두 줄을 한 줄로 표현하면 이거임
people.push(Person(16,"Jun"));
챕터 9: Private / Public 함수
솔리디티에서 함수는 기본적으로 public으로 선언된다.
누구나 또는 다른 어느 컨트랙트가 우리의 컨트랙트의 함수를 호출하고 코드를 실행할 수 있다는 의미다.
하지만 이건 공격에 취약해질 수 있는 가능성이 있어서
기본적으로 함수는 private로 선언하고 공개할 함수만 public으로 선언하는 게 좋다
private함수를 선언하는 방법
unit[] numbers;
function _addToArray(uint _number) private {
numbers.push(_number);
}
private는 컨트랙트 내의 다른 함수들만이 이 함수를 호출해서 numbers 배열로 무언가를 추가할 수 있다걸 의미.
함수 인자명과 마찬가지로 private함수명도 언더바_로 시작하는 것이 관례임.
챕터 10: 함수 더 알아보기
함수의 반환값, 함수의 제어자
반환값
함수에서 어떤 값을 반환받으려면 다음과같이 선언해야함.
string greeting = "what's up dog";
function sayHello() public returns (string) {
//솔리디티에서 함수 선언은 반환값 종류를 포함한다. 지금은 string이야.
return greeting;
}
함수제어자
위에 함수는 솔리디티에서 어떤값을 변경하거나 무언가를 쓰는등 상태를 변화시키지 않는다.
이렇게 함수가 데이터를 보기만 하고 변경하지 않는 건 view함수로 선언한다.
function sayHello() public view returns (string) {
}