접근 제어자 (visibility modifier)
- 함수가 언제, 어디서 호출 될 수 있는지 제어
내부 컨트랙트 | 외부 컨트랙트 | 상속 컨트랙트 | |
Private | O | X | X |
Internal | O | X | O |
Public | O | O | O |
External | X | O | X |
상태 제어자 (state modifier)
- 블록체인과 상호작용 하는 방법을 알려줌
view | - 해당 함수를 실행해도 어떤 데이터도 저장/변경되지 않음 - 그저 읽기만 함 |
pure | - 해당 함수를 실행해도 어떤 데이터도 저장/변경되지 않음 - 어떤 데이터도 읽지 않음 |
payable | - 이더(ether)를 받을 수 있음 |
사용자 정의 제어자
- 함수에 이 제어자들이 어떻게 영향을 줄지를 결정하는 논리구성 가능
위의 제어자들은 아래와 같이 사용 가능
function test() external view onlyOwner anotherModifier { /* ... */ }
Payable
- 함수 제어자
- 이더를 받을 수 있는 특별한 함수 유형
contract OnlineStore {
function buySomething() external payable {
// 함수 실행에 0.001이더가 보내졌는지 확인
require(msg.value == 0.001 ether);
// 보내졌다면, 함수를 호출한 자에게 디지털 아이템을 전달
transferThing(msg.sender);
}
}
* msg.value : 컨트랙트로 이더가 얼마나 보내졌는지 확인하는 방법
* ether : 기본적으로 포함된 단위
// web3.js 에서 아래 함수를 실행하면 위 코드 실행
OnlineStore.buySomething({from: web3.eth.defaultAccount, value: web3.utils.toWei(0.001)})
* web3.js : Dapp의 JS 프론트엔드
* value : 이더(ether)를 얼마나 보낼지 결정 (함수에선 0.001)
* 솔리디티 함수에 payable로 표시되지 않았는데 이더를 보내려고 하면 함수에서 트랜잭션 거부함
출금
- 이더를 보내면 해당 컨트랙트의 이더리움 계좌에 저장됨
- 컨트랙트로부터 이더를 인출하는 함수를 만들어야 인출 가능
contract GetPaid is Ownable {
function withdraw() external onlyOwner {
owner.transfer(this.balance);
}
}
* transfer() : 이더를 특정 주소로 전달 가능
* this.balance : 컨트랙트에 저장되어 있는 전체 잔액 반환
난수 (Random Numbers)
keccak256을 통한 난수 생성 예시 코드
// Generate a random number between 1 and 100:
uint randNonce = 0;
uint random = uint(keccak256(now, msg.sender, randNonce)) % 100;
randNonce++;
uint random2 = uint(keccak256(now, msg.sender, randNonce)) % 100;
1. keccak를 사용해서 값들을 임의의 해쉬 값으로 변환
- now(타임 스탬프 값)
- nonce(딱 한번만 사용되는 숫자)
⇒ 똑같은 입력으로 두 번 이상 동일한 해시 함수 실행할 수 없게하는 역할
2. 변환 값을 uint 값으로 변환
3. %100을 써서 마지막 두자리만 받음
⇒ 0 ~ 99 의 값 얻을 수 있음
위 메소드의 문제점 : 정직하지 않은 노드 공격에 취약
- 컨트랙트 실행 → 트랜잭션으로서 네트워크 노드 하나 혹은 여러 노드에 실행 알림
- 네트워크 노드 : 여러개의 트랜잭션 모으고 "작업증명"으로 알려진 문제 풀려고 시도
- 해당 트랜잭션 그룹을 그들의 작업증명(PoW)과 함께 블록으로 네트워크에 배포
- 한 노드 : PoW를 풀기 성공
- 다른 노드들 : PoW 풀려는 시도 멈춤, 해당 노드가 보낸 트랜잭션이 유효한 것인지 검증
- 유효하다면 해당 블록을 받아들이고 다음 블록 풀려고 시도
⇒ 이것이 난수 함수 발생을 취약하게 만듦
수정 코드
- zombiefactory.sol
pragma solidity ^0.4.19;
import "./ownable.sol";
// Ownable 상속
contract ZombieFactory is Ownable {
// event 선언
event NewZombie(uint zombieId , string name, uint dna);
// dnaModulus라는 uint형 변수를 생성하고 10의 dnaDigits승을 배정
uint dnaDigits = 16;
uint dnaModulus = 10 ** dnaDigits;
// 재사용 대기시간 : 1일이 지나야 다시 사용 가능
uint cooldownTime = 1 days;
// Zombie 라는 구조체 생성
struct Zombie {
string name;
uint dna;
uint32 level;
uint32 readyTime; // 재사용 대기시간
uint16 winCount; // 전투 승리 횟수
uint16 lossCount; // 전투 패배 횟수
}
// Zombie 구조체의 public 배열 생성
Zombie[] public zombies;
// 좀비 id로 좀비를 저장하고 검색
mapping (uint => address) public zombieToOwner;
mapping (address => uint) ownerZombieCount;
// 함수 접근 제어자를 private에서 internal로 변경
function _createZombie(string _name, uint _dna) internal {
// 새로운 좀비를 zombies 배열에 추가
// 배열의 첫 원소가 0이라는 인덱스를 갖기 때문에, array.push() - 1은 막 추가된 좀비의 인덱스가 됨
uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime), 0, 0)) - 1;
zombieToOwner[id] = msg.sender;
ownerZombieCount[msg.sender]++;
// 새로운 좀비가 배열에 추가되면 이벤트 실행
NewZombie(id, _name, _dna);
}
// uint를 반환하는 view 함수 선언
function _generateRandomDna(string _str) private view returns (uint) {
uint rand = uint(keccak256(_str));
// 좀비 DNA가 16자리 숫자이기만을 원하므로 연산 후 값 반환
return rand % dnaModulus;
}
// public 함수 선언
function createRandomZombie(string _name) public {
require(ownerZombieCount[msg.sender] == 0);
// 좀비 dna 생성
uint randDna = _generateRandomDna(_name);
// 새로운 좀비 배열에 추가
_createZombie(_name, randDna);
}
}
- zombiefeeding.sol
pragma solidity ^0.4.19;
// zombiefactory.sol 불러오기
import "./zombiefactory.sol";
// KittyInterface라는 인터페이스를 정의
contract KittyInterface {
function getKitty(uint256 _id) external view returns (
bool isGestating,
bool isReady,
uint256 cooldownIndex,
uint256 nextActionAt,
uint256 siringWithId,
uint256 birthTime,
uint256 matronId,
uint256 sireId,
uint256 generation,
uint256 genes
);
}
// ZombieFactory 상속
contract ZombieFeeding is ZombieFactory {
// 크립토키티 컨트랙트 주소의 업데이트가 가능하도록 수정
KittyInterface kittyContract;
// 좀비 소유자 확인하는 사용자 정의 제어자
modifier ownerOf(uint _zombieId) {
require(msg.sender == zombieToOwner[_zombieId]);
}
// 소유자만 수정할 수 있도록 수정
function setKittyContractAddress(address _address) external onlyOwner {
kittyContract = KittyInterface(_address);
}
// 1일을 초단위로 바꾼것의 합과 같음
function _triggerCooldown(Zombie storage _zombie) internal {
_zombie.readyTime = uint32(now + cooldownTime);
}
// 좀비가 먹이를 먹은 후 충분한 시간이 지났는지 판단해서 알려줌
function _isReady(Zombie storage _zombie) internal view returns(bool) {
return (_zombie.readyTime <= now);
}
// 좀비 dna가 생명체의 dna와 혼합되어 새로운 좀비 생성
function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) internal ownerOf(_zombieId) {
// 먹이를 먹는 좀비의 dna 얻음
Zombie storage myZombie = zombies[_zombieId];
// 좀비가 먹이 먹은 시간을 고려하도록 수정
require(_isReady(myZombie));
_targetDna = _targetDna % dnaModulus; // 16자리보다 크면 안됨
uint newDna = (myZombie + _targetDna) / 2; // 새로운 dna
// 키티 유전자를 가진 좀비는 dna 마지막 2자리를 99로 변경
if(keccak256(_species) == keccak256("kitty")) {
newDna = newDna - newDna % 100 + 99;
}
// 좀비 생성 함수 호출
// 인자로 좀비의 이름과 dna 값을 받음
_createZombie("NoName", newDna);
// 좀비의 재사용 대기시간 만듦
_triggerCooldown(myZombie);
}
// 크립토키티 컨트랙트와 상호작용
// 크립토키티 컨트랙트에서 고양이 유전자를 얻어내는 함수를 생성
function feedOnKitty (uint _zombieId, uint _kittyId) public {
uint kittyDna;
(,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);
// dna가 혼합된 새로운 좀비를 생성하는 함수
feedAndMultiply(_zombieId, kittyDna, "kitty");
}
}
- zombiehelper.sol
pragma solidity ^0.4.19;
import "./zombiefeeding.sol";
contract ZombieHelper is ZombieFeeding {
uint levelUpFee = 0.001 ether;
modifier aboveLevel(uint _level, uint _zombieId) {
// 좀비의 레벨이 입력 받은 레벨보다 높은지 확인
require(zombies[_zombieId].level >= _level);
_;
}
// 이더를 출금할 수 있는 함수
function withdraw() external onlyOwner {
// 컨트랙트에 저장되어 있는 전체 잔액을 특정 주소로 전달
owner.transfer(this.balance);
}
// 이더 가격 변동을 대비해 소유자가 가격 변경 가능하게 하는 함수
function levelUpFee(uint _fee) external onlyOwner {
levelUpFee = _fee; // 레벨업 이더 값 변경
}
// 이더를 받으면 좀비의 레벨을 올려주는 함수
function levelUp(uint _zombieId) external payable {
require(msg.value == levelUpFee); // 함수 실행에 이더가 보내졌는지 확인
zombies[_zombieId].level++; // 확인되면 좀비 레벨 증가
}
// 좀비의 레벨이 2 이상이면 이름 수정 가능
function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) ownerOf(_zombieId) {
zombies[_zombieId].name = _newName;
}
// 좀비의 레벨이 20이상이면 좀비에게 임의의 dna 줄 수 있음
function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) ownerOf(_zombieId) {
zombies[_zombieId].dna = _newDna;
}
// 사용자의 전체 좀비 군대를 반환
function getZombiesByOwner(address _owner) external view returns(uint[]) {
uint[] memory result = new uint[](ownerZombieCount[_owner]);
uint counter = 0;
for (uint i = 0; i < zombies.lengthl; i++) {
if(zombieToOwner[i] == _owner) {
result[counter] = i;
counter++;
}
}
return result;
}
}
- zombieattack.sol
pragma solidity ^0.4.19;
import "./zombiehelper.sol";
contract ZombieBattle is ZombieHelper {
uint randNonce = 0; //난수를 만드는데 사용
uint attackVictoryProbability = 70; // 승리 확률
// 난수 생성 후 반환 함수
function randMod(uint _modulus) internal returns(uint) {
randNonce++; // 똑같은 입력으로 두 번 이상 동일한 해시 함수 실행할 수 없게하는 역할
return uint(keccak256(now, msg.sender, randNonce)) % _modulus;
}
function attack(uint _zombieId, uint _targetId) external ownerOf(_zombieId) {
Zombie storage myZombie = zombies[_zombieId]; // 내 좀비
Zombie storage enemyZombie = zombies[_targetId]; // 상대 좀비
uint rand = randMod(100); // 값에 따라 전투 결과 결정
// 내 좀비 승리
if(rand <= attackVictoryProbability) {
myZombie.winCount++;
myZombie.level++;
enemyZombie.lossCount++;
// 새로운 좀비 생성
feedAndMultiply(_zombieId, enemyZombie.dna, "zombie");
} else {
// 내 좀비 패배
myZombie.lossCount++;
enemyZombie.winCount++;
}
// 좀비 쿨타임
_triggerCooldown(myZombie);
}
}
github : https://github.com/dlwltn98/studyCryptoZombies
cryptoZombies: https://cryptozombies.io/ko/course
'Solidity' 카테고리의 다른 글
SoliditySolidityCryptoZombies 6 (앱 프론트엔드 & Web3.js) 정리 (0) | 2023.02.11 |
---|---|
SolidityCryptoZombies 5 (ERC721 & 크립토 수집품) 정리 (0) | 2023.02.10 |
CryptoZombies 3 (고급 솔리디티 개념) 정리 (0) | 2023.02.08 |
CryptoZombies 2 (좀비가 희생물을 공격하다) 정리 (0) | 2023.02.07 |
CryptoZombies 1 (좀비 공장 만들기) 정리 (0) | 2023.02.06 |